bash - kann ich tun: find ... -exec das && das?

23

Gibt es eine Möglichkeit, zwei Shell-Befehle, die mit find-exec aufgerufen werden, logisch zu kombinieren ?

Um beispielsweise alle .csv- Dateien auszudrucken , die den String foo zusammen mit seinem Vorkommen enthalten, möchte ich Folgendes tun:

find . -iname \*.csv -exec grep foo {} && echo {} \;

aber bash beschwert sich mit "fehlendem argument an '-exec'"

Marcus Junius Brutus
quelle
2
Sie können 2 -execnacheinander oder ein einzelnes verwenden -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;.
JW013,
Das hat mich immer wieder gestolpert: Ich gehe immer davon aus, dass das erste Argument, das an sh(in diesem Fall {}) übergeben wird, so etwas wie ist $1und sein $0wird sh. Aber in der Tat sind Sie richtig, das erste Argument zeigt sich als $0. Das erste Argument als Name des aufrufenden Befehls ist nur eine Konvention, die in diesen Fällen nicht automatisch erzwungen wird.
dubiousjim
Vielleicht sollte mit dieser Frage zusammengeführt werden: unix.stackexchange.com/q/18077/4801
dubiousjim

Antworten:

24

-execist ein Prädikat, das einen Befehl ausführt (keine Shell) und basierend auf dem Ergebnis des Befehls (Beendigungsstatus Null oder Nicht-Null ) als wahr oder falsch ausgewertet wird .

So:

find . -iname '*.csv' -exec grep foo {} \; -print

würde drucken den Dateipfad , wenn grepFunde in der Datei foo. Stattdessen -printkönnen Sie ein anderes -execPrädikat oder ein beliebiges anderes Prädikat verwenden

find . -iname '*.csv' -exec grep foo {} \; -exec echo {} \;

Siehe auch !und -ofinde Operatoren für Negation und oder .

Alternativ können Sie eine Shell wie folgt starten:

find . -iname '*.csv' -exec sh -c '
   grep foo "$1" && echo "$1"' sh {} \;

Oder um zu vermeiden, dass für jede Datei eine Shell gestartet werden muss:

find . -iname '*.csv' -exec sh -c '
  for i do
    grep foo "$i" && echo "$i"
  done' sh {} +
Stéphane Chazelas
quelle
10

Das Problem, mit dem Sie konfrontiert sind, besteht darin, dass die Shell zuerst die Befehlszeile analysiert und zwei einfache Befehle sieht, die vom &&Operator getrennt werden :, find . -iname \*.csv -exec grep foo {}und echo {} \;. Zitiert &&( find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) umgeht das, aber jetzt der Befehl ausgeführt durch findso etwas wie grepmit den Argumenten foo, wibble.csv, &&, echound wibble.csv. Sie müssen anweisen find, eine Shell auszuführen, die den &&Operator interpretiert :

find . -iname \*.csv -exec sh -c 'grep foo "$0" && echo "$0"' {} \;

Beachten Sie, dass das erste Argument nach sh -c SOMECOMMANDist $0, nicht $1.

Sie können die Startzeit eines Shell-Prozesses für jede Datei speichern, indem Sie die Befehlsaufrufe mit gruppieren -exec … +. Übergeben Sie zur Vereinfachung der Verarbeitung einen Dummy-Wert $0, um "$@"die Dateinamen aufzulisten.

find . -iname \*.csv -exec sh -c 'for x in "$@"; do grep foo "$x" && echo "$x"; done' \ {} +

Wenn der Shell-Befehl nur aus zwei durch getrennten Programmen besteht &&, findkann der Job von selbst ausgeführt werden: Schreiben Sie zwei aufeinanderfolgende -execAktionen, und die zweite wird nur ausgeführt, wenn die erste mit dem Status 0 beendet wird.

find . -iname \*.csv -exec grep foo {} \; -exec echo {} \;

(Ich gehe davon aus, dass grepund echonur zu Illustrationszwecken dienen, da -exec echodiese durch ersetzt werden können -printund die resultierende Ausgabe ohnehin nicht besonders nützlich ist.)

Gilles 'SO - hör auf böse zu sein'
quelle
2
Ich neige dazu, "$ 0" zu vermeiden, da es auch von der Shell verwendet wird, um Fehlermeldungen anzuzeigen. Beispielsweise könnte eine verwirrende ./some-file: grep: command not foundFehlermeldung angezeigt werden. -exec find sh -c '... "$1"' sh {} \;hätte das Problem nicht. In Ihrem zweiten Suchbefehl ist ein Tippfehler enthalten.
Stéphane Chazelas
3

In diesem speziellen Fall würde ich tun:

find . -iname \*.csv -exec grep -l foo \{\} \;

Oder wenn Sie ack haben :

ack -al -G '.*\.csv' foo

Um Ihre eigentliche Frage zu beantworten, könnte Folgendes funktionieren:

find . -iname \*.csv -exec sh -c "grep foo {} && echo {}" \;

Dennis Kaarsemaker
quelle
3
Der letztgenannte find -Befehl ist nicht nur nicht portierbar, sondern auch sehr gefährlich, da Dateipfade als Shell-Code ausgewertet werden.
Stéphane Chazelas
Umso mehr Grund, es zu vermeiden, indem man ack / grep richtig benutzt :)
Dennis Kaarsemaker
1
Die Alternative von jw013 (in einem Kommentar zur Frage angegeben) ist in dieser Hinsicht sicherer. (
Ich habe