Ripetere ripetutamente i file in una directory


15

L'iterazione ricorsiva dei file in una directory può essere facilmente eseguita da:

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

Tuttavia, quanto sopra non funziona per cose più complesse, dove è necessario eseguire molti rami condizionali, loop ecc. Lo usavo per quanto sopra:

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

Tuttavia, sembra che questo non funzioni con file contenenti caratteri oscuri:

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

Esiste un'alternativa che gestisce bene personaggi così oscuri?


1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;è un modo migliore per farlo.
jw013,

Puoi risolverlo e mantenere il tuo design originale, cambiando il tuo read linein IFS= read -r line. L'unico personaggio che lo romperà allora è una nuova riga.
Patrick,

1
@Patrick, ma i nomi dei file possono contenere nuove righe. Ecco perché -d $'\0'è preferibile.
godlygeek,

Risposte:


7

Ancora un altro uso sicurofind :

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

(Funziona con qualsiasi POSIX find, ma la parte della shell richiede bash. Con * BSD e GNU find, puoi usare -print0invece di -exec printf '%s\0' {} +, sarà leggermente più veloce.)

Ciò consente di utilizzare l'input standard all'interno del loop e funziona con qualsiasi percorso.


1
Perché ho dovuto cercarlo: "leggi ... Se non viene fornito alcun nome, la riga letta viene assegnata alla variabile REPLY". Quindido echo "Filename is '$REPLY'"
Andrew,

9

Fare questo è semplice come:

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

O...

find -exec executable_script {} \;

5

L'approccio più semplice (ma sicuro) è usare il globbing della shell:

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

Per trasformare la ricorrenza sopra in sottodirectory (in bash), puoi usare l' globstaropzione; anche impostato dotglobper abbinare i file il cui nome inizia con .:

$ 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:

Attenzione che fino alla bash 4.2, **/ricorre in collegamenti simbolici alle directory. Dalla bash 4.3, **/ricorre solo nelle directory, come find.

Un'altra soluzione comune è utilizzare find -print0con 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:

Si noti che h:/gè effettivamente corretto poiché il nome del file contiene a \r.


4

È un po 'difficile da fare il vostro ciclo leggi portabile, ma per bash, in particolare, si può provare qualcosa di simile a questo .

Parte pertinente:

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

Indica finddi stampare l'output delimitato da caratteri NUL (0x00) e readdi recuperare le righe delimitate da NUL ( -d $'\0') senza gestire barre rovesciate come escape per altri caratteri ( -r) e non eseguire alcuna divisione in righe sulle righe ( IFS=). Poiché 0x00 è un byte che non può verificarsi in nomi di file o percorsi in Unix, questo dovrebbe gestire tutti i tuoi strani problemi con i nomi di file.


1
-d ''è equivalente a -d $'\0'.
l0b0,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.