Lakukan secara berulang-ulang melalui file dalam direktori

15

Pengulangan secara berulang melalui file dalam direktori dapat dengan mudah dilakukan dengan:

find . -type f -exec bar {} \;

Namun, di atas tidak bekerja untuk hal-hal yang lebih kompleks, di mana banyak cabang bersyarat, perulangan dll perlu dilakukan. Saya menggunakan ini untuk hal di atas:

while read line; do [...]; done < <(find . -type f)

Namun, sepertinya ini tidak berfungsi untuk file yang berisi karakter tidak jelas:

$ touch $'a\nb'
$ find . -type f
./a?b

Apakah ada alternatif yang menangani karakter tidak jelas seperti itu dengan baik?

pengguna2064000
sumber
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;adalah cara yang lebih baik untuk melakukannya.
jw013
Anda dapat menyelesaikan ini, dan mempertahankan desain asli Anda, dengan mengubah read lineke IFS= read -r line. Satu-satunya karakter yang akan memecahnya adalah baris baru.
Patrick
1
@ Patrick, tetapi nama file dapat berisi baris baru. Itu sebabnya -d $'\0'lebih disukai.
godlygeek

Jawaban:

7

Penggunaan lain untuk keamananfind :

while IFS= read -r -d '' -u 9
do
    [Do something with "$REPLY"]
done 9< <( find . -type f -exec printf '%s\0' {} + )

(Ini berfungsi dengan POSIX apa pun find, tetapi bagian shell memerlukan bash. Dengan * BSD dan GNU find, Anda dapat menggunakannya -print0sebagai gantinya -exec printf '%s\0' {} +, itu akan sedikit lebih cepat.)

Ini memungkinkan untuk menggunakan input standar di dalam loop, dan bekerja dengan jalur apa pun .

l0b0
sumber
1
Karena saya harus mencarinya: "baca ... Jika tidak ada nama yang diberikan, baris baca ditugaskan ke variabel REPLY." Jadido echo "Filename is '$REPLY'"
Andrew,
9

Melakukan ini sesederhana:

find -exec sh -c 'inline script "$0"' {} \;

Atau...

find -exec executable_script {} \;
mikeserv
sumber
5

Pendekatan paling sederhana (namun aman) adalah dengan menggunakan shell globbing:

$ for f in *; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
h:

Untuk membuat perulangan di atas ke dalam subdirektori (dalam bash), Anda dapat menggunakan globstaropsi; juga diatur dotglobuntuk mencocokkan file yang namanya dimulai dengan .:

$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
:foo:
:foo/file1:
:foo/file two:
h:

Berhati-hatilah bahwa hingga bash 4.2, **/berulang menjadi tautan simbolis ke direktori. Sejak bash 4.3, **/berulang hanya ke direktori, seperti find.

Solusi lain yang umum adalah untuk digunakan find -print0dengan xargs -0:

$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e    f:
:./a b:
:./-e:
:./c
d:

Perhatikan bahwa h:/gsebenarnya sudah benar karena nama file berisi a \r.

terdon
sumber
4

Agak sulit untuk melakukan loop baca Anda dengan mudah, tetapi untuk bash khususnya Anda dapat mencoba sesuatu seperti ini .

Bagian yang relevan:

while IFS= read -d $'\0' -r file ; do
        printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)

Itu menginstruksikan finduntuk mencetak keluarannya yang dibatasi oleh karakter NUL (0x00), dan readuntuk mengambil garis yang dibatasi NUL ( -d $'\0') tanpa menangani garis miring terbalik sebagai lolos dari karakter lain ( -r) dan tidak melakukan pemisahan kata pada baris ( IFS=). Karena 0x00 adalah byte yang tidak dapat terjadi di nama file atau jalur di Unix, ini harus menangani semua masalah nama file aneh Anda.

godlygeek
sumber
1
-d ''setara dengan -d $'\0'.
l0b0