Perché il looping sull'output di find è una cattiva pratica?


170

Questa domanda è ispirata

Perché usare un loop di shell per elaborare il testo è considerato una cattiva pratica?

Vedo questi costrutti

for file in `find . -type f -name ...`; do smth with ${file}; done

e

for dir in $(find . -type d -name ...); do smth with ${dir}; done

essere usato qui quasi quotidianamente anche se alcune persone si prendono il tempo di commentare quei post spiegando perché questo tipo di cose dovrebbero essere evitate ...
Vedere il numero di tali post (e il fatto che a volte quei commenti vengano semplicemente ignorati) Ho pensato che avrei potuto anche fare una domanda:

Perché il looping findsull'output delle cattive pratiche e qual è il modo corretto di eseguire uno o più comandi per ogni nome / percorso di file restituito find?


12
Penso che questo sia un po 'come "Non analizzare mai l'output!" - puoi sicuramente fare uno su una tantum, ma sono più un attacco rapido che una qualità di produzione. O, più in generale, sicuramente non essere mai dogmatico.
Bruce Ediger,


Questo dovrebbe essere trasformato in una risposta canonica
Zaid

6
Perché il punto di ritrovamento è quello di rintracciare ciò che trova.
OrangeDog

2
Un punto accessorio: potresti voler inviare l'output a un file e quindi elaborarlo successivamente nello script. In questo modo l'elenco dei file è disponibile per la revisione se è necessario eseguire il debug dello script.
user117529,

Risposte:


87

Il problema

for f in $(find .)

combina due cose incompatibili.

findstampa un elenco di percorsi di file delimitati da caratteri di nuova riga. Mentre l'operatore split + glob che viene invocato quando si lascia quello $(find .)non quotato in quel contesto di elenco lo divide sui caratteri di $IFS(per impostazione predefinita include newline, ma anche spazio e tabulazione (e NUL in zsh)) ed esegue il globbing su ogni parola risultante (tranne in zsh) (e persino l'espansione del controvento in derivati ​​ksh93 o pdksh!).

Anche se lo fai:

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

Questo è ancora sbagliato in quanto il carattere di nuova riga è valido come qualsiasi altro in un percorso di file. L'output di non find -printè semplicemente post-processabile in modo affidabile (tranne che usando qualche trucco contorto, come mostrato qui ).

Ciò significa anche che la shell deve archiviare completamente l'output finde quindi dividerlo + glob (che implica la memorizzazione dell'output una seconda volta in memoria) prima di iniziare a eseguire il loop dei file.

Si noti che find . | xargs cmdha problemi simili (ci sono spazi vuoti, newline, virgolette singole, virgolette doppie e barra rovesciata (e con alcune xargimplementazioni i byte che non fanno parte di caratteri validi) sono un problema)

Alternative più corrette

L'unico modo per utilizzare un forloop sull'output di findsarebbe utilizzare zshche supporti IFS=$'\0'e:

IFS=$'\0'
for f in $(find . -print0)

(sostituirlo -print0con -exec printf '%s\0' {} +per findimplementazioni che non supportano il non standard (ma al giorno d'oggi abbastanza comune) -print0).

Qui, il modo corretto e portatile è usare -exec:

find . -exec something with {} \;

O se somethingpuò accettare più di un argomento:

find . -exec something with {} +

Se hai bisogno di un elenco di file che deve essere gestito da una shell:

find . -exec sh -c '
  for file do
    something < "$file"
  done' find-sh {} +

(attenzione, potrebbe avviarne più di uno sh).

Su alcuni sistemi, è possibile utilizzare:

find . -print0 | xargs -r0 something with

anche se questo ha un piccolo vantaggio rispetto alla sintassi standard e significa somethingche stdinè la pipe o /dev/null.

Un motivo che potresti voler usare potrebbe essere quello di utilizzare l' -Popzione di GNU xargsper l'elaborazione parallela. Il stdinproblema può anche essere risolto con GNU xargscon l' -aopzione con shell che supporta la sostituzione del processo:

xargs -r0n 20 -P 4 -a <(find . -print0) something

ad esempio, per eseguire fino a 4 invocazioni simultanee di somethingciascuna prendendo 20 argomenti di file.

Con zsho bash, un altro modo per eseguire il loop sull'output di find -print0è con:

while IFS= read -rd '' file <&3; do
  something "$file" 3<&-
done 3< <(find . -print0)

read -d '' legge i record delimitati da NUL anziché quelli delimitati da newline.

bash-4.4e sopra può anche archiviare i file restituiti da find -print0in un array con:

readarray -td '' files < <(find . -print0)

L' zshequivalente (che ha il vantaggio di preservare lo findstato di uscita):

files=(${(0)"$(find . -print0)"})

Con zsh, puoi tradurre la maggior parte delle findespressioni in una combinazione di globbing ricorsivo con qualificazioni glob. Ad esempio, il looping find . -name '*.txt' -type f -mtime -1sarebbe:

for file (./**/*.txt(ND.m-1)) cmd $file

O

for file (**/*.txt(ND.m-1)) cmd -- $file

(attenzione alla necessità di --as with **/*, i percorsi dei file non iniziano con ./, quindi potrebbe iniziare con -per esempio).

ksh93e bashalla fine ha aggiunto il supporto per **/(anche se non più fa avanzare forme di globbing ricorsivo), ma ancora non le qualificazioni glob che ne fanno un uso **molto limitato. Inoltre, bashprima di 4.3 segue i collegamenti simbolici quando si discende dall'albero delle directory.

Come per il loop over $(find .), ciò significa anche memorizzare l'intero elenco di file nella memoria 1 . Ciò può essere auspicabile, anche se in alcuni casi quando non si desidera che le azioni sui file influiscano sulla ricerca dei file (come quando si aggiungono altri file che potrebbero finire per essere trovati).

Altre considerazioni su affidabilità / sicurezza

Condizioni di gara

Ora, se stiamo parlando di affidabilità, dobbiamo menzionare le condizioni di gara tra il tempo find/ zshtrova un file e controlla che soddisfi i criteri e il tempo in cui viene utilizzato ( gara TOCTOU ).

Anche quando si discende da un albero di directory, è necessario assicurarsi di non seguire i collegamenti simbolici e di farlo senza la corsa TOCTOU. find( findAlmeno GNU ) lo fa aprendo le directory usando openat()con i O_NOFOLLOWflag giusti (dove supportati) e mantenendo aperto un descrittore di file per ogni directory, zsh/ bash/ kshnon farlo. Quindi, di fronte a un utente malintenzionato che è in grado di sostituire una directory con un collegamento simbolico al momento giusto, si potrebbe finire per discendere la directory sbagliata.

Anche se finddiscende correttamente la directory, con -exec cmd {} \;e ancora di più con -exec cmd {} +, una volta cmdeseguita, ad esempio come cmd ./foo/baro cmd ./foo/bar ./foo/bar/baz, al momento in cui si cmdutilizza ./foo/bar, gli attributi di barpotrebbero non soddisfare più i criteri corrispondenti find, ma anche peggio, ./foopotrebbero essere stati sostituito da un link simbolico in qualche altro posto (e la finestra della gara è molto più grande con -exec {} +dove findattende di avere abbastanza file da chiamare cmd).

Alcune findimplementazioni hanno un -execdirpredicato (non ancora standard) per alleviare il secondo problema.

Con:

find . -execdir cmd -- {} \;

find chdir()s nella directory principale del file prima di eseguirlo cmd. Invece di chiamare cmd -- ./foo/bar, chiama cmd -- ./bar( cmd -- barcon alcune implementazioni, quindi il --), quindi il problema con la ./foomodifica in un collegamento simbolico viene evitato. Ciò rende l'utilizzo di comandi come rmpiù sicuro (potrebbe comunque rimuovere un file diverso, ma non un file in una directory diversa), ma non comandi che possono modificare i file a meno che non siano stati progettati per non seguire i collegamenti simbolici.

-execdir cmd -- {} +a volte funziona anche ma con diverse implementazioni tra cui alcune versioni di GNU find, è equivalente a -execdir cmd -- {} \;.

-execdir ha anche il vantaggio di aggirare alcuni dei problemi associati ad alberi di directory troppo profondi.

Nel:

find . -exec cmd {} \;

la dimensione del percorso assegnato cmdaumenterà con la profondità della directory in cui si trova il file. Se quella dimensione diventa più grande di PATH_MAX(qualcosa come 4k su Linux), allora qualsiasi chiamata di sistema che cmdfa su quel percorso fallirà con un ENAMETOOLONGerrore.

Con -execdir, ./viene passato solo il nome del file (eventualmente con il prefisso ) cmd. I nomi dei file stessi sulla maggior parte dei file system hanno un limite molto più basso ( NAME_MAX) di PATH_MAX, quindi ENAMETOOLONGè meno probabile che si verifichi l' errore.

Byte vs caratteri

Inoltre, spesso trascurato quando si considera la sicurezza intorno finde più in generale con la gestione dei nomi dei file in generale è il fatto che sulla maggior parte dei sistemi simili a Unix, i nomi dei file sono sequenze di byte (qualsiasi valore di byte tranne 0 in un percorso di file e sulla maggior parte dei sistemi ( Per quelli basati su ASCII, per ora ignoreremo quelli rari basati su EBCDIC) 0x2f è il delimitatore del percorso).

Spetta alle applicazioni decidere se vogliono considerare quei byte come testo. E generalmente lo fanno, ma generalmente la traduzione da byte a caratteri avviene in base alle impostazioni locali dell'utente, in base all'ambiente.

Ciò significa che un determinato nome file può avere una rappresentazione testuale diversa a seconda della locale. Ad esempio, la sequenza di byte 63 f4 74 e9 2e 74 78 74dovrebbe essere côté.txtper un'applicazione che interpreta quel nome di file in una locale in cui il set di caratteri è ISO-8859-1 e cєtщ.txtin una locale in cui invece il set di caratteri è IS0-8859-5.

Peggio. In un locale in cui il set di caratteri è UTF-8 (la norma al giorno d'oggi), 63 f4 74 e9 2e 74 78 74 semplicemente non poteva essere mappato sui caratteri!

findè una di queste applicazioni che considera i nomi dei file come testo per i suoi -name/ -pathpredicati (e altro, come -inameo -regexcon alcune implementazioni).

Ciò significa che, ad esempio, con diverse findimplementazioni (inclusa GNU find).

find . -name '*.txt'

non trova il nostro 63 f4 74 e9 2e 74 78 74file sopra quando viene chiamato in una locale UTF-8 poiché *(che corrisponde a 0 o più caratteri , non byte) non può corrispondere a quei non caratteri.

LC_ALL=C find... aggirerebbe il problema poiché la locale C implica un byte per carattere e (generalmente) garantisce che tutti i valori di byte siano associati a un carattere (anche se probabilmente non definiti per alcuni valori di byte).

Ora, quando si tratta di eseguire il loop su quei nomi di file da una shell, quel byte vs carattere può anche diventare un problema. In genere vediamo 4 tipi principali di shell al riguardo:

  1. Quelli che non sono ancora consapevoli del multi-byte come dash. Per loro, un byte è mappato a un personaggio. Ad esempio, in UTF-8, côtésono 4 caratteri, ma 6 byte. In una locale in cui UTF-8 è il set di caratteri, in

    find . -name '????' -exec dash -c '
      name=${1##*/}; echo "${#name}"' sh {} \;
    

    findtroverà correttamente i file il cui nome è composto da 4 caratteri codificati in UTF-8, ma dashriporterebbe lunghezze comprese tra 4 e 24.

  2. yash: l'opposto. Si tratta solo di personaggi . Tutto l'input che serve viene tradotto internamente in caratteri. Crea la shell più coerente, ma significa anche che non può far fronte a sequenze di byte arbitrarie (quelle che non si traducono in caratteri validi). Anche nella locale C, non può far fronte a valori di byte superiori a 0x7f.

    find . -exec yash -c 'echo "$1"' sh {} \;
    

    in una localizzazione UTF-8 non riuscirà ad esempio sul nostro ISO-8859-1 côté.txtda precedenti.

  3. Quelli come basho in zshcui il supporto multi-byte è stato aggiunto progressivamente. Torneranno a considerare byte che non possono essere associati a caratteri come se fossero caratteri. Hanno ancora alcuni bug qua e là, specialmente con set di caratteri multi-byte meno comuni come GBK o BIG5-HKSCS (quelli che sono abbastanza cattivi poiché molti dei loro caratteri multi-byte contengono byte nell'intervallo 0-127 (come i caratteri ASCII) ).

  4. Quelli come shFreeBSD (almeno 11) o mksh -o utf8-modeche supportano multi-byte, ma solo per UTF-8.

Appunti

1 Per completezza, potremmo menzionare un modo bizzarro di zshfare il loop dei file usando il globbing ricorsivo senza memorizzare l'intero elenco in memoria:

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmdè un qualificatore glob che chiama cmd(in genere una funzione) con il percorso del file corrente in $REPLY. La funzione restituisce true o false per decidere se il file deve essere selezionato (e può anche modificare $REPLYo restituire più file in un $replyarray). Qui eseguiamo l'elaborazione in quella funzione e restituiamo false in modo che il file non sia selezionato.


Se zsh e bash sono disponibili, si può essere meglio solo utilizzando globbing e shell costrutti invece di cercare di contorcere finda comportarsi in modo sicuro. Globbing è sicuro per impostazione predefinita mentre find non è sicuro per impostazione predefinita.
Kevin,

@ Kevin, vedi modifica.
Stéphane Chazelas,

182

Perché il looping findsull'output è una cattiva pratica?

La semplice risposta è:

Perché i nomi dei file possono contenere qualsiasi carattere.

Pertanto, non esiste un carattere stampabile che è possibile utilizzare in modo affidabile per delimitare i nomi dei file.


Le nuove righe vengono spesso utilizzate (in modo errato) per delimitare i nomi dei file, poiché è insolito includere caratteri di nuova riga nei nomi dei file.

Tuttavia, se costruisci il tuo software attorno a presupposti arbitrari, nella migliore delle ipotesi non riesci a gestire casi insoliti e, nel peggiore dei casi, ti apri agli exploit dannosi che danno il controllo del tuo sistema. Quindi è una questione di robustezza e sicurezza.

Se riesci a scrivere software in due modi diversi e uno di questi gestisce correttamente i casi limite (input insoliti), ma l'altro è più facile da leggere, potresti obiettare che c'è un compromesso. (Non lo farei. Preferisco il codice corretto.)

Tuttavia, se la versione corretta e robusta del codice è anche facile da leggere, non ci sono scuse per la scrittura di codice che fallisce nei casi limite. Questo è il caso finde la necessità di eseguire un comando su ogni file trovato.


Cerchiamo di essere più specifici: su un sistema UNIX o Linux, i nomi dei file possono contenere qualsiasi carattere tranne un /(che viene utilizzato come separatore del componente del percorso) e non possono contenere un byte null.

Un byte null è quindi l' unico modo corretto per delimitare i nomi dei file.


Poiché GNU findinclude un -print0primario che utilizzerà un byte null per delimitare i nomi dei file che stampa, GNU find può essere tranquillamente usato con GNU xargse il suo -0flag (e -rflag) per gestire l'output di find:

find ... -print0 | xargs -r0 ...

Tuttavia, non esiste alcun motivo valido per utilizzare questo modulo, perché:

  1. Aggiunge una dipendenza dai rilevamenti GNU che non ha bisogno di essere lì, e
  2. findè progettato per essere in grado di eseguire comandi sui file che trova.

Inoltre, GNU xargsrichiede -0e -r, mentre FreeBSD xargsrichiede solo -0(e non ha -ropzioni), e alcuni xargsnon lo supportano -0affatto. Quindi è meglio attenersi alle funzioni POSIX di find(vedere la sezione successiva) e saltare xargs.

Per quanto riguarda il punto 2 find, la capacità di eseguire comandi sui file che trova, penso che Mike Loukides l'abbia detto meglio:

findL 'attività commerciale sta valutando le espressioni, non individuando i file. Sì, findindividua sicuramente i file; ma questo è davvero solo un effetto collaterale.

--Unix Power Tools


POSIX usi specificati di find

Qual è il modo corretto di eseguire uno o più comandi per ciascuno dei findrisultati?

Per eseguire un singolo comando per ogni file trovato, utilizzare:

find dirname ... -exec somecommand {} \;

Per eseguire più comandi in sequenza per ciascun file trovato, in cui il secondo comando deve essere eseguito solo se il primo comando ha esito positivo, utilizzare:

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

Per eseguire un singolo comando su più file contemporaneamente:

find dirname ... -exec somecommand {} +

find in combinazione con sh

Se è necessario utilizzare le funzionalità della shell nel comando, come reindirizzare l'output o rimuovere un'estensione dal nome file o qualcosa di simile, è possibile utilizzare il sh -ccostrutto. Dovresti sapere alcune cose al riguardo:

  • Non incorporare mai{} direttamente nel shcodice. Ciò consente l'esecuzione di codice arbitrario da nomi di file creati in modo pericoloso. Inoltre, POSIX non ha nemmeno specificato che funzionerà affatto. (Vedi il prossimo punto.)

  • Non utilizzare {}più volte o utilizzarlo come parte di un argomento più lungo. Questo non è portatile. Ad esempio, non farlo:

    find ... -exec cp {} somedir/{}.bak \;

    Per citare le specifiche POSIX perfind :

    Se un nome_utilità o una stringa argomento contiene i due caratteri "{}", ma non solo i due caratteri "{}", viene definito dall'implementazione se find sostituisce quei due caratteri o utilizza la stringa senza modifiche.

    ... Se è presente più di un argomento contenente i due caratteri "{}", il comportamento non è specificato.

  • Gli argomenti che seguono la stringa di comando della shell passata -call'opzione sono impostati sui parametri posizionali della shell, a partire da$0 . Non a partire da $1.

    Per questo motivo, è bene includere un $0valore "fittizio" , ad esempio find-sh, che verrà utilizzato per la segnalazione degli errori all'interno della shell generata. Inoltre, ciò consente l'uso di costrutti come "$@"quando si passano più file alla shell, mentre omettere un valore per $0significherebbe che il primo file passato verrebbe impostato $0e quindi non incluso in "$@".


Per eseguire un singolo comando shell per file, utilizzare:

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

Tuttavia, in genere fornisce prestazioni migliori per gestire i file in un ciclo di shell in modo da non generare una shell per ogni singolo file trovato:

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

(Si noti che for f doequivale for f in "$@"; doe gestisce ciascuno dei parametri posizionali a sua volta, in altre parole, utilizza ciascuno dei file trovati da find, indipendentemente da eventuali caratteri speciali nei loro nomi.)


Ulteriori esempi di findutilizzo corretto :

(Nota: sentiti libero di estendere questo elenco.)


5
C'è un caso in cui non conosco un'alternativa findall'output dell'analisi - in cui è necessario eseguire comandi nella shell corrente (ad esempio perché si desidera impostare le variabili) per ciascun file. In questo caso, while IFS= read -r -u3 -d '' file; do ... done 3< <(find ... -print0)è il miglior linguaggio che conosca. Note: <( )non è portatile - usa bash o zsh. Inoltre, -u3e 3<ci sono nel caso in cui qualcosa all'interno del ciclo cerchi di leggere lo stdin.
Gordon Davisson,

1
@GordonDavisson, forse, ma che cosa è necessario impostare quelle variabili per ? Direi che qualunque cosa sia dovrebbe essere gestita all'interno della find ... -execchiamata. O semplicemente usa un glob shell, se gestirà il tuo caso d'uso.
Carattere jolly

1
Vorrei spesso stampare un riepilogo dopo aver elaborato i file ("2 convertiti, 3 ignorati, i seguenti file avevano errori: ...") e quei conteggi / elenchi devono essere accumulati nelle variabili della shell. Inoltre, ci sono situazioni in cui voglio creare una matrice di nomi di file in modo da poter fare cose più complesse di un'iterazione (in quel caso lo è filelist=(); while ... do filelist+=("$file"); done ...).
Gordon Davisson,

3
La tua risposta è corretta Comunque non mi piace il dogma. Anche se lo so meglio, ci sono molti casi d'uso (specialmente interattivi) in cui è sicuro e più semplice digitare il loop findsull'output o anche peggio ls. Lo sto facendo ogni giorno senza problemi. Conosco le opzioni -print0, --null, -z o -0 di tutti i tipi di strumenti. Ma non perderei tempo a usarli sul mio prompt della shell interattiva a meno che non sia veramente necessario. Questo potrebbe essere notato anche nella tua risposta.
rudimeier

16
@rudimeier, l'argomento sul dogma contro le migliori pratiche è già stato messo a morte . Non interessato. Se lo usi in modo interattivo e funziona bene, va bene per te, ma non lo promuoverò. La percentuale di autori di script che si preoccupano di imparare cos'è un codice robusto e quindi lo fanno solo quando scrivono script di produzione, invece di fare semplicemente ciò che sono abituati a fare in modo interattivo, è estremamente minima. Il trattamento è promuovere le migliori pratiche in ogni momento. Le persone devono imparare che esiste un modo corretto di fare le cose.
Carattere jolly

10

Questa risposta è per set di risultati molto grandi e riguarda principalmente le prestazioni, ad esempio quando si ottiene un elenco di file su una rete lenta. Per piccole quantità di file (diciamo qualche 100 o forse anche 1000 su un disco locale) la maggior parte di questo è discutibile.

Parallelismo e utilizzo della memoria

A parte le altre risposte fornite, relative a problemi di separazione e simili, esiste un altro problema

for file in `find . -type f -name ...`; do smth with ${file}; done

La parte all'interno dei backtick deve essere valutata per prima, prima di essere suddivisa nelle interruzioni di riga. Ciò significa che, se si ottiene una grande quantità di file, è possibile che si verifichino dei limiti di dimensione nei vari componenti; potresti esaurire la memoria se non ci sono limiti; e in ogni caso devi aspettare fino a quando l'intero elenco è stato emesso finde poi analizzato forprima ancora di eseguire il tuo primo smth.

Il modo unix preferito è quello di lavorare con pipe, che sono intrinsecamente in esecuzione in parallelo e che non hanno bisogno di buffer arbitrariamente enormi in generale. Ciò significa: preferiresti che l' findesecuzione fosse parallela alla tua smth, e manterrai il nome del file corrente nella RAM solo quando lo trasmette smth.

Una soluzione almeno in parte OKish per questo è la suddetta find -exec smth. Elimina la necessità di mantenere tutti i nomi dei file in memoria e funziona bene in parallelo. Sfortunatamente, avvia anche un smthprocesso per file. Se smthpuò funzionare solo su un file, è così che deve essere.

Se possibile, sarebbe la soluzione ottimale find -print0 | smth, smthessendo in grado di elaborare i nomi dei file sul suo STDIN. Quindi hai un solo smthprocesso, non importa quanti file ci siano, e devi bufferizzare solo una piccola quantità di byte (qualunque sia il buffering intrinseco del pipe) tra i due processi. Naturalmente, questo è piuttosto irrealistico se si smthtratta di un comando Unix / POSIX standard, ma potrebbe essere un approccio se lo si scrive da soli.

Se ciò non è possibile, find -print0 | xargs -0 smthè probabilmente una delle soluzioni migliori. Come @ dave_thompson_085 menzionato nei commenti, xargssuddivide gli argomenti su più esecuzioni di smthquando vengono raggiunti i limiti di sistema (per impostazione predefinita, nell'intervallo di 128 KB o qualsiasi limite imposto dal execsistema) e ha opzioni per influenzare quanti i file vengono dati a una chiamata di smth, quindi trovare un equilibrio tra numero di smthprocessi e ritardo iniziale.

EDIT: rimosso le nozioni di "migliore" - è difficile dire se qualcosa di meglio comparirà. ;)


find ... -exec smth {} +è la soluzione.
Carattere jolly

find -print0 | xargs smthnon funziona affatto, ma find -print0 | xargs -0 smth(nota -0) o find | xargs smthse i nomi di file non hanno virgolette di spazi bianchi o la barra rovesciata ne esegue uno smthcon tutti i nomi di file disponibili e si adatta a un elenco di argomenti ; se superi i maxarg, viene eseguito smthtutte le volte necessarie per gestire tutti gli argomenti forniti (nessun limite). È possibile impostare piccoli 'blocchi' (quindi un po 'di parallelismo precedente) con -L/--max-lines -n/--max-args -s/--max-chars.
dave_thompson_085


4

Uno dei motivi è che lo spazio bianco lancia una chiave inglese nelle opere, facendo valutare il file 'foo bar' come 'foo' e 'bar'.

$ ls -l
-rw-rw-r-- 1 ec2-user ec2-user 0 Nov  7 18:24 foo bar
$ for file in `find . -type f` ; do echo filename $file ; done
filename ./foo
filename bar
$

Funziona bene se viene utilizzato -exec

$ find . -type f -exec echo filename {} \;
filename ./foo bar
$ find . -type f -exec stat {} \;
  File: ‘./foo bar’
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: ca01h/51713d    Inode: 9109        Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)
Access: 2016-11-07 18:24:42.027554752 +0000
Modify: 2016-11-07 18:24:42.027554752 +0000
Change: 2016-11-07 18:24:42.027554752 +0000
 Birth: -
$

Soprattutto nel caso in cui findesiste un'opzione per eseguire un comando su ogni file è facilmente l'opzione migliore.
Centimane,

1
Considera anche -exec ... {} \;contro-exec ... {} +
thrig

1
se usi for file in "$(find . -type f)" e echo "${file}"poi funziona anche con gli spazi bianchi, altri personaggi speciali credo che causino più problemi però
mazs

9
@mazs - no, la citazione non fa quello che pensi. In una directory con più file provare for file in "$(find . -type f)";do printf '%s %s\n' name: "${file}";donequale dovrebbe (secondo te) stampare ogni nome di file su una riga separata preceduta da name:. Non
don_crissti,

2

Perché l'output di qualsiasi comando è una singola stringa, ma il tuo loop ha bisogno di una matrice di stringhe su cui eseguire il loop. La ragione per cui "funziona" è che le conchiglie tradiscono tradizionalmente la stringa su spazi bianchi per te.

In secondo luogo, a meno che tu non abbia bisogno di una caratteristica particolare di find, tieni presente che molto probabilmente la tua shell può già espandere un modello glob ricorsivo da solo e, soprattutto, che si espanderà in un array adeguato.

Esempio di Bash:

shopt -s nullglob globstar
for i in **
do
    echo «"$i"»
done

Lo stesso nei pesci:

for i in **
    echo «$i»
end

Se hai bisogno delle funzionalità di find, assicurati di dividere solo su NUL (come il find -print0 | xargs -r0linguaggio).

I pesci possono iterare l'output delimitato da NUL. Quindi questo in realtà non è male:

find -print0 | while read -z i
    echo «$i»
end

Come ultimo piccolo gotcha, in molte shell (non ovviamente Fish), il loop over dell'output del comando renderà il corpo del loop una subshell (il che significa che non è possibile impostare una variabile in alcun modo che sia visibile dopo che il loop termina), che è mai quello che vuoi.


@don_crissti Precisely. In genere non funziona. Stavo cercando di essere sarcastico dicendo che "funziona" (tra virgolette).
user2394284,

Nota che il globbing ricorsivo è nato nei zshprimi anni '90 (anche se ne avresti bisogno **/*). fishcome le precedenti implementazioni della funzione equivalente di bash seguono però i collegamenti simbolici quando si discende dall'albero delle directory. Vedere Il risultato di ls *, ls ** e ls *** per le differenze tra le implementazioni.
Stéphane Chazelas,

1

Passare in rassegna l'output di find non è una cattiva pratica: ciò che è cattiva pratica (in questa e in tutte le situazioni) è assumere che il tuo input sia un formato particolare invece di sapere (testare e confermare) che è un formato particolare.

TLDR / cbf: find | parallel stuff

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.