Jawaban yang diterima / terpilih adalah bagus, tetapi mereka kekurangan beberapa detail yang rumit. Posting ini membahas kasus-kasus tentang bagaimana menangani dengan lebih baik ketika ekspansi shell-path-name (glob) gagal, ketika nama-nama file mengandung baris baru / simbol dasbor dan memindahkan output perintah re-direction keluar dari for-loop saat menulis hasil ke mengajukan.
Ketika menjalankan ekspansi shell glob menggunakan *
ada kemungkinan ekspansi gagal jika tidak ada file yang ada dalam direktori dan string glob yang tidak diperluas akan diteruskan ke perintah yang akan dijalankan, yang bisa memiliki hasil yang tidak diinginkan. The bash
shell menyediakan pilihan shell diperpanjang untuk menggunakan ini nullglob
. Jadi loop pada dasarnya menjadi sebagai berikut di dalam direktori yang berisi file-file Anda
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Ini memungkinkan Anda keluar dari loop for aman ketika ekspresi ./*
tidak mengembalikan file apa pun (jika direktori kosong)
atau dengan cara POSIX compliant ( nullglob
adalah bash
khusus)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Ini memungkinkan Anda masuk ke dalam loop ketika ekspresi gagal untuk sekali dan kondisi [ -f "$file" ]
memeriksa apakah string yang tidak diperluas ./*
adalah nama file yang valid di direktori itu, yang tidak akan. Jadi pada kegagalan kondisi ini, menggunakan continue
kami melanjutkan kembali ke for
loop yang tidak akan berjalan selanjutnya.
Perhatikan juga penggunaan --
sesaat sebelum melewati argumen nama file. Ini diperlukan karena seperti yang disebutkan sebelumnya, nama file shell dapat berisi tanda hubung di mana saja dalam nama file. Beberapa perintah shell menafsirkannya dan memperlakukannya sebagai opsi perintah ketika nama tidak dikutip dengan benar dan mengeksekusi perintah berpikir jika bendera disediakan.
Tanda- --
tanda akhir opsi baris perintah dalam kasus yang berarti, perintah tidak boleh mengurai string di luar titik ini sebagai flag perintah tetapi hanya sebagai nama file.
Mengutip nama file dengan benar memecahkan kasus ketika nama tersebut mengandung karakter gumpal atau spasi putih. Tetapi nama file * nix juga dapat berisi baris baru di dalamnya. Jadi kami membatasi nama file dengan satu-satunya karakter yang tidak dapat menjadi bagian dari nama file yang valid - byte nol ( \0
). Karena secara bash
internal menggunakan C
string gaya di mana byte nol digunakan untuk menunjukkan akhir string, itu adalah kandidat yang tepat untuk ini.
Jadi menggunakan printf
opsi shell untuk membatasi file dengan byte NULL ini menggunakan -d
opsi read
perintah, bisa kita lakukan di bawah ini
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
The nullglob
dan printf
melilit (..)
yang berarti mereka pada dasarnya dijalankan dalam sub-shell (shell anak), karena untuk menghindari nullglob
pilihan untuk merenungkan shell orang tua, setelah keluar perintah. The -d ''
pilihan untuk read
perintah tidak POSIX compliant, sehingga membutuhkan bash
shell untuk ini harus dilakukan. Menggunakan find
perintah ini dapat dilakukan sebagai
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Untuk find
implementasi yang tidak mendukung -print0
(selain implementasi GNU dan FreeBSD), ini dapat ditiru dengan menggunakanprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Perbaikan penting lainnya adalah memindahkan re-direction dari for-loop untuk mengurangi jumlah file I / O yang tinggi. Ketika digunakan di dalam loop, shell harus menjalankan system-calls dua kali untuk setiap iterasi for-loop, sekali untuk membuka dan sekali untuk menutup deskriptor file yang terkait dengan file. Ini akan menjadi leher botol pada kinerja Anda untuk menjalankan iterasi besar. Saran yang disarankan adalah memindahkannya di luar loop.
Memperluas kode di atas dengan perbaikan ini, bisa Anda lakukan
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
yang pada dasarnya akan meletakkan isi perintah Anda untuk setiap iterasi input file Anda ke stdout dan ketika loop berakhir, buka file target sekali untuk menulis isi stdout dan simpan. Setara find
versi yang sama akan
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out