Masalah
for f in $(find .)
menggabungkan dua hal yang tidak kompatibel.
findmencetak daftar jalur file yang dibatasi oleh karakter baris baru. Sementara operator split + glob yang dipanggil ketika Anda membiarkan tanda $(find .)kutip dalam konteks daftar membaginya pada karakter $IFS(secara default termasuk baris baru, tetapi juga spasi dan tab (dan NUL dalam zsh)) dan melakukan globbing pada setiap kata yang dihasilkan (kecuali in zsh) (dan bahkan menguatkan ekspansi di turunan ksh93 atau pdksh!).
Bahkan jika Anda berhasil:
IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion in pdksh
# but not ksh93)
for f in $(find .) # invoke split+glob
Itu masih salah karena karakter baris baru sama validnya dengan yang ada di jalur file. Output dari find -printtidak pas-proses dapat diandalkan (kecuali dengan menggunakan beberapa trik berbelit-belit, seperti yang ditunjukkan di sini ).
Itu juga berarti shell perlu menyimpan output findsepenuhnya, dan kemudian membagi + glob itu (yang menyiratkan menyimpan output yang kedua kalinya dalam memori) sebelum mulai untuk mengulang file.
Catatan yang find . | xargs cmdmemiliki masalah serupa (ada, kosong, baris baru, kutipan tunggal, kutipan ganda dan backslash (dan dengan beberapa xargbyte implementasi tidak membentuk bagian dari karakter yang valid) adalah masalah)
Alternatif yang lebih benar
Satu-satunya cara untuk menggunakan forloop pada output findadalah dengan menggunakan zshyang mendukung IFS=$'\0'dan:
IFS=$'\0'
for f in $(find . -print0)
(ganti -print0dengan -exec printf '%s\0' {} +untuk findimplementasi yang tidak mendukung non-standar (tapi cukup umum saat ini) -print0).
Di sini, cara yang benar dan portabel adalah dengan menggunakan -exec:
find . -exec something with {} \;
Atau jika somethingdapat mengambil lebih dari satu argumen:
find . -exec something with {} +
Jika Anda membutuhkan daftar file yang akan ditangani oleh shell:
find . -exec sh -c '
for file do
something < "$file"
done' find-sh {} +
(Waspadalah mungkin mulai lebih dari satu sh).
Pada beberapa sistem, Anda dapat menggunakan:
find . -print0 | xargs -r0 something with
meskipun memiliki sedikit keuntungan atas sintaks standar dan berarti something's stdinadalah baik pipa atau /dev/null.
Salah satu alasan Anda mungkin ingin menggunakannya adalah menggunakan -Popsi GNU xargsuntuk pemrosesan paralel. The stdinMasalah juga dapat bekerja di sekitar dengan GNU xargsdengan -apilihan dengan kerang mendukung substitusi proses:
xargs -r0n 20 -P 4 -a <(find . -print0) something
misalnya, untuk menjalankan hingga 4 doa bersamaan dari somethingmasing - masing mengambil 20 argumen file.
Dengan zshatau bash, cara lain untuk mengulang keluaran find -print0adalah dengan:
while IFS= read -rd '' file <&3; do
something "$file" 3<&-
done 3< <(find . -print0)
read -d '' membaca NUL catatan terbatas bukan yang dibatasi baris baru.
bash-4.4dan di atas juga dapat menyimpan file yang dikembalikan oleh find -print0dalam array dengan:
readarray -td '' files < <(find . -print0)
The zshsetara (yang memiliki keuntungan dari melestarikan find's status exit):
files=(${(0)"$(find . -print0)"})
Dengan zsh, Anda dapat menerjemahkan sebagian besar findekspresi ke kombinasi globbing rekursif dengan kualifikasi glob. Misalnya, pengulangan find . -name '*.txt' -type f -mtime -1adalah:
for file (./**/*.txt(ND.m-1)) cmd $file
Atau
for file (**/*.txt(ND.m-1)) cmd -- $file
(waspadalah terhadap perlunya --dengan **/*, jalur file tidak dimulai dengan ./, jadi mungkin mulai dengan -misalnya).
ksh93dan bashakhirnya menambahkan dukungan untuk **/(meskipun tidak lebih memajukan bentuk globbing rekursif), tetapi masih bukan kualifikasi glob yang membuat penggunaan **sangat terbatas di sana. Hati-hati juga bahwa bashsebelum 4.3 mengikuti symlink ketika turun pohon direktori.
Seperti untuk mengulang $(find .), itu juga berarti menyimpan seluruh daftar file dalam memori 1 . Itu mungkin diinginkan meskipun dalam beberapa kasus ketika Anda tidak ingin tindakan Anda pada file memiliki pengaruh pada pencarian file (seperti ketika Anda menambahkan lebih banyak file yang akhirnya dapat ditemukan sendiri).
Pertimbangan keandalan / keamanan lainnya
Kondisi balapan
Sekarang, jika kita berbicara tentang keandalan, kita harus menyebutkan kondisi balapan antara waktu find/ zshmenemukan file dan memeriksa apakah itu memenuhi kriteria dan waktu itu digunakan ( perlombaan TOCTOU ).
Bahkan ketika menurunkan pohon direktori, kita harus memastikan untuk tidak mengikuti symlink dan melakukannya tanpa perlombaan TOCTOU. find( findSetidaknya GNU ) melakukannya dengan membuka direktori menggunakan openat()dengan O_NOFOLLOWflag yang tepat (jika didukung) dan menjaga deskriptor file terbuka untuk setiap direktori, zsh/ bash/ kshjangan lakukan itu. Jadi, di hadapan penyerang yang bisa mengganti direktori dengan symlink pada waktu yang tepat, Anda bisa berakhir pada direktori yang salah.
Sekalipun findturun direktori dengan benar, dengan -exec cmd {} \;dan bahkan lebih lagi dengan -exec cmd {} +, sekali cmddieksekusi, misalnya sebagai cmd ./foo/baratau cmd ./foo/bar ./foo/bar/baz, pada saat cmdmemanfaatkan ./foo/bar, atribut barmungkin tidak lagi memenuhi kriteria yang cocok dengan find, tetapi lebih buruk lagi, ./foomungkin telah digantikan oleh symlink ke beberapa tempat lain (dan jendela balapan dibuat jauh lebih besar dengan -exec {} +tempat findmenunggu untuk memiliki cukup file untuk dipanggil cmd).
Beberapa findimplementasi memiliki -execdirpredikat ( belum standar) untuk mengatasi masalah kedua.
Dengan:
find . -execdir cmd -- {} \;
find chdir()s ke direktori induk file sebelum menjalankan cmd. Alih-alih memanggil cmd -- ./foo/bar, ia memanggil cmd -- ./bar( cmd -- bardengan beberapa implementasi, maka itu --), sehingga masalah dengan ./foodiubah menjadi symlink dihindari. Itu membuat menggunakan perintah seperti rmlebih aman (itu masih bisa menghapus file yang berbeda, tetapi bukan file di direktori yang berbeda), tetapi bukan perintah yang dapat memodifikasi file kecuali mereka dirancang untuk tidak mengikuti symlink.
-execdir cmd -- {} +terkadang juga berfungsi tetapi dengan beberapa implementasi termasuk beberapa versi GNU find, itu setara dengan -execdir cmd -- {} \;.
-execdir juga memiliki manfaat mengatasi beberapa masalah yang terkait dengan pohon direktori terlalu dalam.
Di:
find . -exec cmd {} \;
ukuran lintasan yang diberikan untuk cmdakan tumbuh dengan kedalaman direktori file. Jika ukuran itu menjadi lebih besar dari PATH_MAX(sesuatu seperti 4k di Linux), maka setiap panggilan sistem yang cmddilakukan pada lintasan itu akan gagal dengan ENAMETOOLONGkesalahan.
Dengan -execdir, hanya nama file (mungkin diawali dengan ./) dilewatkan ke cmd. Nama file sendiri pada sebagian besar sistem file memiliki batas ( NAME_MAX) yang jauh lebih rendah daripada PATH_MAX, sehingga ENAMETOOLONGkesalahan lebih kecil kemungkinannya terjadi.
Bytes vs karakter
Juga, sering diabaikan ketika mempertimbangkan keamanan sekitar finddan lebih umum dengan menangani nama file secara umum adalah kenyataan bahwa pada kebanyakan sistem mirip Unix, nama file adalah urutan byte (nilai byte apa pun kecuali 0 dalam jalur file, dan pada sebagian besar sistem ( Yang berbasis ASCII, kami akan mengabaikan yang berbasis langka EBCDIC untuk saat ini) 0x2f adalah pembatas jalur).
Terserah aplikasi untuk memutuskan apakah mereka ingin mempertimbangkan byte tersebut sebagai teks. Dan mereka umumnya melakukannya, tetapi umumnya terjemahan dari byte ke karakter dilakukan berdasarkan lokal pengguna, berdasarkan lingkungan.
Apa itu artinya bahwa nama file yang diberikan mungkin memiliki representasi teks yang berbeda tergantung pada lokal. Sebagai contoh, urutan byte 63 f4 74 e9 2e 74 78 74akan côté.txtuntuk aplikasi yang menafsirkan nama file itu di lokal di mana set karakter adalah ISO-8859-1, dan cєtщ.txtdi lokal di mana charset adalah IS0-8859-5 sebagai gantinya.
Lebih buruk. Di lokal di mana charset adalah UTF-8 (norma saat ini), 63 f4 74 e9 2e 74 78 74 tidak bisa dipetakan ke karakter!
findadalah salah satu aplikasi tersebut yang menganggap nama file sebagai teks untuk nya -name/ -pathpredikat (dan lebih, seperti -inameatau -regexdengan beberapa implementasi).
Apa artinya itu misalnya, dengan beberapa findimplementasi (termasuk GNU find).
find . -name '*.txt'
tidak akan menemukan 63 f4 74 e9 2e 74 78 74file kami di atas ketika dipanggil di lokal UTF-8 karena *(yang cocok dengan 0 atau lebih karakter , bukan byte) tidak dapat cocok dengan yang bukan karakter.
LC_ALL=C find... akan mengatasi masalah karena C locale menyiratkan satu byte per karakter dan (umumnya) menjamin bahwa semua nilai byte memetakan ke karakter (meskipun mungkin tidak terdefinisi untuk beberapa nilai byte).
Sekarang ketika datang untuk mengulangi nama-nama file dari shell, byte vs karakter itu juga bisa menjadi masalah. Kami biasanya melihat 4 jenis kerang utama dalam hal ini:
Yang masih belum sadar multi-byte suka dash. Bagi mereka, byte memetakan sebuah karakter. Misalnya, dalam UTF-8, côtéadalah 4 karakter, tetapi 6 byte. Di lokal tempat UTF-8 adalah rangkaian karakter, di
find . -name '????' -exec dash -c '
name=${1##*/}; echo "${#name}"' sh {} \;
findakan berhasil menemukan file yang namanya terdiri dari 4 karakter yang dikodekan dalam UTF-8, tetapi dashakan melaporkan panjangnya berkisar antara 4 dan 24.
yash: sebaliknya. Ini hanya berurusan dengan karakter . Semua input yang dibutuhkan diterjemahkan secara internal ke karakter. Itu membuat shell yang paling konsisten, tetapi juga berarti ia tidak bisa mengatasi urutan byte sewenang-wenang (yang tidak diterjemahkan ke karakter yang valid). Bahkan di lokal C, ia tidak bisa mengatasi nilai byte di atas 0x7f.
find . -exec yash -c 'echo "$1"' sh {} \;
di lokal UTF-8 akan gagal pada ISO-8859-1 kami côté.txtdari sebelumnya misalnya.
Mereka yang suka bashatau di zshmana dukungan multi-byte telah semakin ditambahkan. Itu akan kembali ke mengingat byte yang tidak dapat dipetakan ke karakter seolah-olah mereka adalah karakter. Mereka masih memiliki beberapa bug di sana-sini terutama dengan charset multi-byte yang kurang umum seperti GBK atau BIG5-HKSCS (yang cukup buruk karena banyak karakter multi-byte mereka mengandung byte dalam rentang 0-127 (seperti karakter ASCII) ).
Mereka seperti shFreeBSD (setidaknya 11) atau mksh -o utf8-modeyang mendukung multi-byte tetapi hanya untuk UTF-8.
Catatan
1 Untuk kelengkapan, kami dapat menyebutkan cara hacky zshuntuk mengulang file menggunakan globing rekursif tanpa menyimpan seluruh daftar dalam memori:
process() {
something with $REPLY
false
}
: **/*(ND.m-1+process)
+cmdadalah kualifikasi global yang memanggil cmd(biasanya suatu fungsi) dengan jalur file saat ini di $REPLY. Fungsi mengembalikan benar atau salah untuk memutuskan apakah file harus dipilih (dan juga dapat mengubah $REPLYatau mengembalikan beberapa file dalam $replyarray). Di sini kita melakukan pemrosesan dalam fungsi itu dan mengembalikan false sehingga file tidak dipilih.