これを実行するには、いくつかの実行可能な方法があります。
元のバージョンに固執したい場合は、次の方法で行うことができます。
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
これは、ファイル名にリテラルの改行が含まれている場合でも失敗しますが、スペースによって改行されることはありません。
ただし、IFSをいじる必要はありません。これがこれを行うための私の好ましい方法です:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
もし発見した場合< <(command)
について、あなたは読むべき不慣れな構文プロセス置換を。これに対する利点はfor file in $(find ...)
、スペース、改行、その他の文字を含むファイルが正しく処理されることです。これは、find
with -print0
がnull
(別名\0
)を各ファイル名のターミネーターとして使用し、改行とは異なり、nullはファイル名の正当な文字ではないため機能します。
ほぼ同等のバージョンに対するこれの利点
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
それは、whileループの本体での変数割り当てが保持されるということですか。つまり、while
上記のようにパイプすると、の本体はwhile
サブシェルにあるため、希望どおりにならない可能性があります。
プロセス置換バージョンの利点find ... -print0 | xargs -0
はごくわずかです。1 xargs
行を印刷するか、ファイルに対して1つの操作を実行するだけでよい場合はバージョンで問題ありませんが、複数のステップを実行する必要がある場合は、ループバージョンの方が簡単です。
編集:これは素晴らしいテストスクリプトですので、この問題を解決するさまざまな試みの違いを理解することができます
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"