Le risposte accettate / votate con successo sono ottime, ma mancano di alcuni dettagli chiacchieroni. Questo post tratta i casi su come gestire meglio quando l'espansione del nome percorso della shell (glob) fallisce, quando i nomi dei file contengono nuovi simboli incorporati di linee / trattini e spostando l'output del comando dalla direzione for-loop quando si scrivono i risultati in un file.
Quando si esegue l'espansione shell glob usando *
v'è la possibilità per l'espansione di sicuro se sono presenti file presenti nella directory e un glob stringa non-espanso viene passato al comando da eseguire, che potrebbe avere risultati indesiderati. La bash
shell fornisce un'opzione shell estesa per questo utilizzo nullglob
. Quindi il ciclo diventa sostanzialmente come segue all'interno della directory che contiene i tuoi file
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Ciò consente di uscire in sicurezza dal ciclo for quando l'espressione ./*
non restituisce alcun file (se la directory è vuota)
o in modo conforme a POSIX ( nullglob
è bash
specifico)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Questo ti permette di entrare nel ciclo quando l'espressione fallisce per una volta e la condizione [ -f "$file" ]
controlla se la stringa non espansa ./*
è un nome file valido in quella directory, che non lo sarebbe. Quindi, in caso di errore di questa condizione, l'utilizzo continue
riprendiamo al for
ciclo che non verrà eseguito successivamente.
Si noti inoltre l'utilizzo di --
poco prima di passare l'argomento nome file. Ciò è necessario perché, come notato in precedenza, i nomi dei file della shell possono contenere trattini in qualsiasi punto del nome file. Alcuni dei comandi della shell lo interpretano e li trattano come un'opzione di comando quando il nome non è quotato correttamente ed esegue il comando pensando che sia fornito il flag.
In questo caso --
segnala la fine delle opzioni della riga di comando, il che significa che il comando non deve analizzare nessuna stringa oltre questo punto come flag di comando ma solo come nomi di file.
La doppia citazione dei nomi dei file risolve correttamente i casi in cui i nomi contengono caratteri glob o spazi bianchi. Ma i nomi di file * nix possono contenere anche nuove righe. Quindi limitiamo i nomi di file con l'unico carattere che non può far parte di un nome file valido: il byte null ( \0
). Poiché bash
utilizza internamente C
stringhe di stile in cui vengono utilizzati i byte null per indicare la fine della stringa, è il candidato giusto per questo.
Quindi usando l' printf
opzione della shell per delimitare i file con questo byte NULL usando l' -d
opzione del read
comando, possiamo fare di seguito
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Il nullglob
e il printf
sono racchiusi, il (..)
che significa che sono fondamentalmente eseguiti in una sotto-shell (shell figlio), perché per evitare l' nullglob
opzione di riflettere sulla shell padre, una volta che il comando termina. L' -d ''
opzione di read
comando non è conforme a POSIX, quindi per farlo è necessaria una bash
shell. Usando il find
comando questo può essere fatto come
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Per le find
implementazioni che non supportano -print0
(diverse dalle implementazioni GNU e FreeBSD), questo può essere emulato usandoprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Un'altra soluzione importante è spostare la reindirizzamento fuori dal for-loop per ridurre un numero elevato di I / O di file. Se utilizzata all'interno del ciclo, la shell deve eseguire due volte le chiamate di sistema per ogni iterazione del ciclo for, una volta per aprire e una volta per chiudere il descrittore di file associato al file. Questo diventerà un collo di bottiglia sulle tue prestazioni per eseguire iterazioni di grandi dimensioni. Il suggerimento consigliato sarebbe di spostarlo al di fuori del ciclo.
Estendendo il codice sopra con queste correzioni, potresti farlo
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
che fondamentalmente metterà il contenuto del tuo comando per ogni iterazione del tuo input di file su stdout e quando il ciclo termina, apri il file di destinazione una volta per scrivere il contenuto dello stdout e salvarlo. La find
versione equivalente della stessa sarebbe
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