Come faccio a non riuscire a trovare se -exec fallisce?


29

Quando eseguo questo comando nella shell (in una directory non vuota):

find . -exec invalid_command_here {} \;

Capisco questo:

find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory

(e così via per ogni file)

Devo findfallire dopo il primo errore. C'è un modo per farlo funzionare? Non posso usare xargs, poiché ho spazi nel mio percorso, ma ho bisogno che lo script lo chiami per restituire un codice di errore.

Risposte:


34

Questa è una limitazione di find. Lo standard POSIX specifica che lo stato di ritorno findè 0 a meno che non si sia verificato un errore durante l'attraversamento delle directory; lo stato di ritorno dei comandi eseguiti non entra in esso.

Puoi fare in modo che i comandi scrivano il loro stato su un file o su un descrittore:

find_status_file=$(mktemp findstatus)
: >"$find_status_file"
find  -exec sh -c 'trap "echo \$?" EXIT; invalid_command "$0"' {} \;
if [ -s "$find_status_file" ]; then
  echo 1>&2 "An error occurred"
fi
rm -f "$find_status_file"

Un altro metodo, come hai scoperto , è usare xargs. I xargscomandi elaborano sempre tutti i file, ma restituiscono lo stato 1 se uno qualsiasi dei comandi restituisce uno stato diverso da zero.

find  -print0 | xargs -0 -n1 invalid_command

Ancora un altro metodo è quello di evitare finde usare il globbing ricorsivo nella shell: **/significa qualsiasi profondità delle sottodirectory. Ciò richiede la versione 4 o successiva di bash; macOS è bloccato alla versione 3.x quindi dovresti installarlo da una collezione di porte. Utilizzare set -eper arrestare lo script sul primo comando che restituisce uno stato diverso da zero.

shopt -s globstar
set -e
for x in **/*.xml; do invalid_command "$x"; done

Attenzione che in bash da 4.0 a 4.2 funziona, ma attraversa i collegamenti simbolici alle directory, cosa che di solito non è desiderabile.

Se usi zsh invece di bash, il globbing ricorsivo funziona fuori dalla scatola senza gotchas. Zsh è disponibile per impostazione predefinita su OSX / macOS. In zsh, puoi semplicemente scrivere

set -e
for x in **/*.xml; do invalid_command "$x"; done

L' xargsapproccio funziona in generale, ma in qualche modo si rompe sui bash -ccomandi. Ad esempio: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Questo viene eseguito più volte mentre find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}viene eseguito una volta e non riesce. Qualche idea sul perché?
DKroot l'

@DKroot Non usare mai {}all'interno bash -c. Questo prende il nome del file e lo inserisce direttamente all'interno del comando shell. Se il nome del file contiene caratteri che hanno un significato speciale nella shell come gli spazi, la shell interpreta questi caratteri speciali come tali. Se hai bisogno di una shell, passa {}come argomento separato, ad esempio bash -c 'foo "$0"' {}(nota anche le doppie virgolette $0).
Gilles 'SO- smetti di essere cattivo' l'

OK, citando le domande a parte, perché quanto segue non si ferma al primo errore ?? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot il

@DKroot Perché dovrebbe fermarsi su un errore? xargs esegue sempre il comando su tutti gli elementi.
Gilles 'SO- smetti di essere malvagio' il

Sto cercando di usare questa risposta: l' find . -print0 | xargs -0 -n1 invalid_commandapproccio xargs ( ). Questo si ferma sul primo errore correttamente: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Grande! Ma lo stesso approccio non funziona con bash -c(sopra). L'unica differenza tra i due è bash -c.
DKroot,

18

Posso usare questo invece:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command

4

xargsè un'opzione. Tuttavia, in realtà è banalmente facile farlo findanche usando +invece di\;

-exec  utility_name  [argument ...]   {} +

Dalla documentazione POSIX :

Se l'espressione primaria è punteggiata da un segno più, il primario deve sempre essere considerato vero e i nomi dei percorsi per i quali viene valutato il primario devono essere aggregati in insiemi. L'utilità utility_name deve essere invocata una volta per ogni serie di percorsi aggregati. Ogni invocazione deve iniziare dopo che è stato aggregato l'ultimo percorso nell'insieme e deve essere completato prima che l'utilità di ricerca esca e prima che il primo percorso nell'insieme successivo (se presente) sia aggregato per questo primario, ma non è altrimenti specificato se l'invocazione si verifica prima, durante o dopo le valutazioni di altre primarie. Se qualsiasi invocazione restituisce un valore diverso da zero come stato di uscita, l'utilità di ricerca deve restituire uno stato di uscita diverso da zero.Un argomento contenente solo i due caratteri "{}" deve essere sostituito dall'insieme di nomi di percorso aggregati, con ciascun nome di percorso passato come argomento separato all'utilità invocata nello stesso ordine in cui è stato aggregato. Le dimensioni di qualsiasi set di due o più nomi di percorso devono essere limitate in modo tale che l'esecuzione dell'utilità non provochi il superamento del limite {ARG_MAX} del sistema. Se è presente più di un argomento contenente solo i due caratteri "{}", il comportamento non è specificato.

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.