Ada juga solusi yang sangat sederhana:andalkan bash globbing
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
$ ls
stupid file 3 stupid file1 stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid file 3'
file: 'stupid file1'
file: 'stupid file2'
Perhatikan bahwa saya tidak yakin perilaku ini adalah yang default tetapi saya tidak melihat pengaturan khusus di shopt saya jadi saya akan pergi dan mengatakan bahwa itu harus "aman" (diuji pada osx dan ubuntu).
Ada beberapa cara yang bisa diterapkan untuk mencapai hal ini.
Jika Anda ingin tetap menggunakan versi aslinya, ini dapat dilakukan dengan cara ini:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Ini masih akan gagal jika nama file memiliki baris baru literal di dalamnya, tetapi spasi tidak akan merusaknya.
Namun, mengotak-atik IFS tidak perlu. Inilah cara pilihan saya untuk melakukan ini:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Jika Anda menemukan < <(command)
sintaks asing Anda harus membaca tentang substitusi proses. Keuntungan dari for file in $(find ...)
ini apakah file dengan spasi, baris baru, dan karakter lain ditangani dengan benar. Ini berfungsi karena find
dengan -print0
akan menggunakan null
(alias \0
) sebagai terminator untuk setiap nama file dan, tidak seperti baris baru, null bukanlah karakter resmi dalam nama file.
Keuntungan dari ini dibandingkan versi yang hampir setara
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Apakah itu penugasan variabel apa pun di badan while loop dipertahankan. Yaitu, jika Anda mem-pipe ke while
seperti di atas maka badan dari while
ada dalam subkulit yang mungkin bukan yang Anda inginkan.
Keuntungan dari versi substitusi proses dibandingkan find ... -print0 | xargs -0
minimal:xargs
version baik-baik saja jika yang Anda butuhkan hanyalah mencetak satu baris atau melakukan satu operasi pada file, tetapi jika Anda perlu melakukan beberapa langkah, versi loop lebih mudah.
EDIT :Ini skrip pengujian yang bagus sehingga Anda bisa mendapatkan gambaran tentang perbedaan antara berbagai upaya untuk memecahkan masalah ini
#!/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"
Anda dapat mengganti perulangan berbasis kata dengan perulangan berbasis baris:
find . -iname "foo*" | while read f
do
# ... loop body
done