Wie bringe ich find zum Scheitern, wenn -exec fehlschlägt?

29

Wenn ich diesen Befehl in der Shell ausführe (in einem nicht leeren Verzeichnis):

find . -exec invalid_command_here {} \;

Ich bekomme das:

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

(und so weiter für jede Datei)

Ich muss findnach dem ersten Fehler scheitern. Gibt es eine Möglichkeit, dies zum Laufen zu bringen? Ich kann nicht verwenden xargs, da ich Leerzeichen in meinem Pfad habe, aber ich brauche das Skript, das dies aufruft, um einen Fehlercode zurückzugeben.

Steven Fisher
quelle

Antworten:

34

Dies ist eine Einschränkung von find. Der POSIX-Standard gibt an, dass der Rückgabestatus find0 ist, es sei denn, beim Durchlaufen der Verzeichnisse ist ein Fehler aufgetreten. Der Rückgabestatus von ausgeführten Befehlen wird nicht berücksichtigt.

Sie können Befehle veranlassen, ihren Status in eine Datei oder einen Deskriptor zu schreiben:

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"

Wie Sie festgestellt haben , besteht eine andere Methode darin, xargs zu verwenden. Der xargsBefehl verarbeitet immer alle Dateien, gibt jedoch den Status 1 zurück, wenn einer der Befehle einen Status ungleich Null zurückgibt.

find  -print0 | xargs -0 -n1 invalid_command

Eine weitere Methode besteht darin, findstattdessen rekursives Globbing in der Shell zu vermeiden und zu verwenden: **/bedeutet eine beliebige Tiefe von Unterverzeichnissen. Dies erfordert Version 4 oder höher von bash. Da macOS bei Version 3.x nicht mehr funktioniert, muss es von einer Port-Sammlung installiert werden. Dient set -ezum Anhalten des Skripts beim ersten Befehl, der einen Status ungleich Null zurückgibt.

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

Beachten Sie, dass dies in Bash 4.0 bis 4.2 funktioniert, jedoch symbolische Verknüpfungen zu Verzeichnissen durchläuft, was normalerweise nicht wünschenswert ist.

Wenn Sie zsh anstelle von bash verwenden, funktioniert das rekursive Globbing ohne Abstriche. Zsh ist standardmäßig unter OSX / macOS verfügbar. In zsh kann man einfach schreiben

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles 'SO - hör auf böse zu sein'
quelle
Der xargsAnsatz funktioniert im Allgemeinen, bricht aber irgendwie bei bash -cBefehlen ab. Zum Beispiel: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Dies wird mehrmals ausgeführt, während find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}es einmal ausgeführt wird und fehlschlägt. Irgendeine Idee warum?
DKroot
@DKroot Niemals {}drinnen verwenden bash -c. Dadurch wird der Dateiname übernommen und direkt in den Shell-Befehl eingefügt. Wenn der Dateiname Zeichen enthält, die in der Shell eine besondere Bedeutung haben, z. B. Leerzeichen, interpretiert die Shell diese Sonderzeichen als solche. Wenn Sie eine Shell benötigen, übergeben Sie diese {}als separates Argument, z. B. bash -c 'foo "$0"' {}(beachten Sie auch die doppelten Anführungszeichen $0).
Gilles 'SO - hör auf böse zu sein'
OK, abgesehen von Fragen, warum hört das Folgende nicht beim ersten Fehler auf? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@DKroot Warum sollte es bei einem Fehler aufhören? xargs führt den Befehl immer für alle Elemente aus.
Gilles 'SO - hör auf böse zu sein'
Ich versuche, diese Antwort zu verwenden: der xargs ( find . -print0 | xargs -0 -n1 invalid_command) -Ansatz. Dadurch wird auf dem ersten Fehler richtig: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Groß! Aber der gleiche Ansatz funktioniert nicht mit bash -c(oben). Der einzige Unterschied zwischen den beiden ist bash -c.
DKroot
18

Ich kann dies stattdessen verwenden:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Steven Fisher
quelle
4

xargsist eine Option. Es ist jedoch eigentlich ganz einfach, dies auch zu tun, findindem Sie +anstelle von verwenden\;

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

Aus der POSIX-Dokumentation :

Wenn der primäre Ausdruck durch ein Pluszeichen unterbrochen ist, wird der primäre Ausdruck immer als wahr ausgewertet, und die Pfadnamen, für die der primäre Ausdruck ausgewertet wird, werden zu Mengen zusammengefasst. Das Dienstprogramm utility_name wird einmal für jeden Satz aggregierter Pfadnamen aufgerufen. Jeder Aufruf beginnt, nachdem der letzte Pfadname in der Menge aggregiert wurde, und wird abgeschlossen, bevor das Suchdienstprogramm beendet wird und bevor der erste Pfadname in der nächsten Menge (falls vorhanden) für diesen Primärnamen aggregiert wird. Andernfalls wird nicht angegeben, ob der Aufruf erfolgt tritt vor, während oder nach den Bewertungen anderer Vorwahlen auf. Wenn ein Aufruf einen Wert ungleich Null als Beendigungsstatus zurückgibt, gibt das Dienstprogramm find einen Beendigungsstatus ungleich Null zurück.Ein Argument, das nur die zwei Zeichen "{}" enthält, wird durch die Menge der aggregierten Pfadnamen ersetzt, wobei jeder Pfadname in derselben Reihenfolge, in der er aggregiert wurde, als separates Argument an das aufgerufene Dienstprogramm übergeben wird. Die Größe eines Satzes von zwei oder mehr Pfadnamen ist so zu begrenzen, dass durch die Ausführung des Dienstprogramms das {ARG_MAX} -Limit des Systems nicht überschritten wird. Wenn mehr als ein Argument vorhanden ist, das nur die beiden Zeichen "{}" enthält, ist das Verhalten nicht spezifiziert.

schweizerisch
quelle