Verständnis der -exec-Option von "find"

53

Ich bin ständig auf der Suche nach der Syntax von

find . -name "FILENAME"  -exec rm {} \;

hauptsächlich, weil ich nicht sehe, wie genau das -execTeil funktioniert. Was bedeuten die Klammern, der Backslash und das Semikolon? Gibt es andere Anwendungsfälle für diese Syntax?

Zsolt Szilagy
quelle
11
@ Philippos: Ich verstehe deinen Standpunkt. Bitte beachten Sie, dass die Manpages eine Referenz sind, dh nützlich für diejenigen, die mit der Materie vertraut sind, um die Syntax nachzuschlagen. Für jemanden, der neu in diesem Thema ist, sind sie oft zu kryptisch und formal, um nützlich zu sein. Sie werden feststellen, dass die akzeptierte Antwort etwa zehnmal so lang ist wie der Manpage-Eintrag, und das hat einen Grund.
Zsolt Szilagy
6
Sogar auf der alten POSIX- manSeite steht: Ein Dienstprogrammname oder ein Argument, das nur die beiden Zeichen "{}" enthält, soll durch den aktuellen Pfadnamen ersetzt werden , der mir ausreicht. Außerdem hat es ein Beispiel mit -exec rm {} \;, genau wie in deiner Frage. Zu meiner Zeit gab es kaum andere Ressourcen als die "große graue Wand", Bücher mit gedruckten manSeiten (Papier war billiger als Lagerung). Ich weiß also, dass dies für jemanden ausreicht, der mit dem Thema noch nicht vertraut ist. Ihre letzte Frage ist hier allerdings fair zu stellen. Leider haben weder @Kusalananda noch ich eine Antwort darauf.
Philippos
1
Komm schon @Philippos. Erzählst du Kusalananda wirklich, dass er die Manpage nicht verbessert hat? :-)
Zsolt Szilagy
1
@allo Auch wenn xargses manchmal praktisch ist, findkönnen mehrere Pfadargumente an den Befehl übergeben werden, ohne dass dieser angegeben wird. -exec command... {} +(mit +statt \;) übergibt so viele Pfade, command...wie es passt (jedes Betriebssystem hat seine eigene Begrenzung für die Länge einer Befehlszeile). Und wie xargsdie +terminierte Form von find‚s -execAktion wird auch läuft command...mehrere Male in dem seltenen Fall , dass es zu viele Pfade innerhalb der Grenze zu passen.
Eliah Kagan
2
@ZsoltSzilagy Das habe ich weder gesagt noch gemeint. Er hat dich sehr gut gefüttert, ich denke nur, du bist alt genug, um alleine zu essen. (-;
Philippos

Antworten:

90

Diese Antwort besteht aus folgenden Teilen:

  • Grundsätzliche Verwendung von -exec
  • Verwendung -execin Kombination mitsh -c
  • Verwenden -exec ... {} +
  • Verwenden -execdir

Grundsätzliche Verwendung von -exec

Die -execOption verwendet ein externes Dienstprogramm mit optionalen Argumenten als Argument und führt es aus.

Wenn der String {}irgendwo im angegebenen Befehl vorhanden ist, wird jede Instanz durch den Pfadnamen ersetzt, der gerade verarbeitet wird (z ./some/path/FILENAME. B. ). In den meisten Shells müssen die beiden Zeichen {}nicht in Anführungszeichen gesetzt werden.

Der Befehl muss mit einem beendet werden, ;um findzu wissen, wo er endet (da es danach möglicherweise weitere Optionen gibt). Um die Datei ;vor der Shell zu schützen , muss sie in Anführungszeichen als \;oder gesetzt ';'werden. Andernfalls wird sie von der Shell als Ende des findBefehls betrachtet.

Beispiel (die \am Ende der ersten beiden Zeilen stehen nur für Zeilenfortsetzungen):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

Dies findet alle regulären Dateien ( -type f), deren Namen mit dem Muster *.txtim oder unter dem aktuellen Verzeichnis übereinstimmen . Es wird dann geprüft, ob der String helloin einer der gefundenen Dateien vorkommt grep -q(was keine Ausgabe erzeugt, nur einen Exit-Status). Für diejenigen Dateien, die den String enthalten, catwird ausgeführt, um den Inhalt der Datei an das Terminal auszugeben.

Jeder -execverhält sich auch wie ein "Test" für die von gefundenen Pfadnamen find, genau wie -typeund -name. Wenn der Befehl einen Beendigungsstatus von Null zurückgibt (was "Erfolg" bedeutet), wird der nächste Teil des findBefehls berücksichtigt, andernfalls wird der findBefehl mit dem nächsten Pfadnamen fortgesetzt. Dies wird im obigen Beispiel verwendet, um Dateien zu finden, die die Zeichenfolge enthalten hello, aber um alle anderen Dateien zu ignorieren.

Das obige Beispiel zeigt die beiden häufigsten Anwendungsfälle von -exec:

  1. Als Test, um die Suche weiter einzuschränken.
  2. Ausführen einer Aktion für den gefundenen Pfadnamen (normalerweise, aber nicht unbedingt am Ende des findBefehls).

Verwendung -execin Kombination mitsh -c

Der Befehl, der ausgeführt werden -execkann, ist auf ein externes Dienstprogramm mit optionalen Argumenten beschränkt. Es -execist nicht möglich, Shell-Built-Ins, Funktionen, Bedingungen, Pipelines, Umleitungen usw. direkt mit zu verwenden, es sei denn, sie sind in eine Art sh -cChild-Shell eingebunden.

Wenn bashFunktionen erforderlich sind, verwenden Sie bash -canstelle von sh -c.

sh -cWird /bin/shmit einem Skript ausgeführt, das in der Befehlszeile angegeben wird, gefolgt von optionalen Befehlszeilenargumenten für dieses Skript.

Ein einfaches Beispiel für die Verwendung sh -cohne find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

Dies übergibt zwei Argumente an das untergeordnete Shell-Skript:

  1. Die Zeichenfolge sh. Dies ist wie $0im Skript verfügbar , und wenn die interne Shell eine Fehlermeldung ausgibt, wird dieser Zeichenfolge ein Präfix vorangestellt.

  2. Das Argument applesist als verfügbar $1im Drehbuch, und hatte es mehr Argumente gewesen, dann hätten diese als verfügbar gewesen $2, $3usw. Sie würden auch in der Liste vorhanden sein "$@"(außer $0denen nicht Teil sein würde "$@").

Dies ist in Kombination mit nützlich, -execda damit beliebig komplexe Skripte erstellt werden können, die auf die von gefundenen Pfadnamen angewendet werden find.

Beispiel: Suchen Sie alle regulären Dateien mit einem bestimmten Dateinamensuffix und ändern Sie dieses Dateinamensuffix in ein anderes Suffix, wobei die Suffixe in Variablen gespeichert werden:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'

Innerhalb des internen Skripts $1wäre das die Zeichenfolge text, $2wäre die Zeichenfolge txtund $3wäre der Pfadname find, der für uns gefunden wurde. Die Parametererweiterung ${3%.$1}würde den Pfadnamen übernehmen und das Suffix .textdaraus entfernen .

Oder mit dirname/ basename:

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

oder mit hinzugefügten Variablen im internen Skript:

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2; pathname=$3
    mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'

Beachten Sie, dass sich in dieser letzten Variante die Variablen fromund toin der untergeordneten Shell von den gleichnamigen Variablen im externen Skript unterscheiden.

Das Obige ist die richtige Art, ein beliebiges komplexes Skript von -execmit aufzurufen find. Verwenden Sie findin einer Schleife wie

for pathname in $( find ... ); do

ist fehleranfällig und unelegant (persönliche Meinung). Es teilt Dateinamen auf Leerzeichen auf, ruft das Globbing von Dateinamen auf und zwingt die Shell, das gesamte Ergebnis zu erweitern, findbevor sie überhaupt die erste Iteration der Schleife ausführt.

Siehe auch:


Verwenden -exec ... {} +

Das ;am Ende darf durch ersetzt werden +. Dies führt finddazu, dass der angegebene Befehl mit möglichst vielen Argumenten (gefundenen Pfadnamen) ausgeführt wird und nicht einmal für jeden gefundenen Pfadnamen. Die Zeichenfolge {} muss unmittelbar vor dem auftreten, +damit dies funktioniert .

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

Hier findwerden die resultierenden Pfadnamen gesammelt und catauf so viele von ihnen wie möglich gleichzeitig ausgeführt.

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

Ebenso wird hier mvso wenig wie möglich ausgeführt. Dieses letzte Beispiel erfordert GNU mvvon coreutils (was die -tOption unterstützt ).

Das Verwenden von -exec sh -c ... {} +ist auch eine effiziente Möglichkeit, eine Reihe von Pfadnamen mit einem beliebig komplexen Skript zu durchlaufen.

Die Grundlagen sind die gleichen wie bei der Verwendung -exec sh -c ... {} ';', aber das Skript benötigt jetzt eine viel längere Liste von Argumenten. Diese können durch Überlaufen "$@"innerhalb des Skripts durchlaufen werden.

Unser Beispiel aus dem letzten Abschnitt, in dem Dateinamensuffixe geändert werden:

from=text  #  Find files that have names like something.text
to=txt     #  Change the .text suffix to .txt

find . -type f -name "*.$from" -exec sh -c '
    from=$1; to=$2
    shift 2  # remove the first two arguments from the list
             # because in this case these are *not* pathnames
             # given to us by find
    for pathname do  # or:  for pathname in "$@"; do
        mv "$pathname" "${pathname%.$from}.$to"
    done' sh "$from" "$to" {} +

Verwenden -execdir

Es gibt auch -execdir(von den meisten findVarianten implementiert , aber keine Standardoption).

Dies funktioniert -execmit dem Unterschied, dass der angegebene Shell-Befehl mit dem Verzeichnis des gefundenen Pfadnamens als aktuellem Arbeitsverzeichnis ausgeführt wird und {}den Basisnamen des gefundenen Pfadnamens ohne Pfad enthält (GNU findgeht dem Basisnamen jedoch weiterhin voran ./, während BSD aktiv ist findwerde das nicht tun).

Beispiel:

find . -type f -name '*.txt' \
    -execdir mv {} done-texts/{}.done \;

Dadurch wird jede gefundene *.txt-Datei in ein bereits vorhandenes done-textsUnterverzeichnis im selben Verzeichnis verschoben, in dem die Datei gefunden wurde . Die Datei wird auch umbenannt, indem das Suffix hinzugefügt .donewird.

Dies wäre etwas kniffliger, -execda wir den Basisnamen der gefundenen Datei herausfinden müssten, um {}den neuen Namen der Datei zu bilden. Wir benötigen auch den Verzeichnisnamen von {}, um das done-textsVerzeichnis richtig zu lokalisieren .

Mit -execdirwerden einige Dinge wie diese einfacher.

Die entsprechende Operation using -execanstelle von -execdirmüsste eine untergeordnete Shell verwenden:

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

oder,

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +
Kusalananda
quelle
7
-execNimmt ein Programm und Argumente und führt es aus. Einige Shell-Befehle bestehen nur aus einem Programm und Argumenten, viele jedoch nicht. Ein Shell-Befehl kann Umleitung und Piping enthalten. -execkann nicht (obwohl das ganze findumgeleitet werden kann). Ein Shell-Befehl kann ; && ifetc verwenden. -execkann nicht, obwohl -a -oeinige tun können. Ein Shell-Befehl kann ein Alias, eine Shell-Funktion oder eine integrierte Funktion sein. -execkann nicht. Ein Shell-Befehl kann vars erweitern. -execkann nicht (obwohl die äußere Schale, die die findDose läuft ). Ein Shell-Befehl kann $(command)jedes Mal anders eingesetzt werden. -execkann nicht. ...
dave_thompson_085
... Ein Shell-Befehl kann glob, -execnicht - obwohl finder Dateien genauso durchlaufen kann wie die meisten Globs, ist das also selten erwünscht.
Dave_thompson_085
@ Dave_Thompson_085 Natürlich kann der Shell-Befehl sich shselbst sein, der in der Lage ist, all diese Dinge zu tun
Tavian Barnes
2
Zu sagen , es ist ein Shell - Befehl hier falsch ist, find -exec cmd arg \;ruft nicht eine Shell eine Shell - Befehlszeile zu interpretieren, es läuft execlp("cmd", "arg")direkt, nicht execlp("sh", "-c", "cmd arg")(für die würde die Schale tun das Äquivalent am Ende , execlp("cmd", "arg")wenn cmdnicht builtin).
Stéphane Chazelas
2
Sie könnten klarstellen, dass alle findArgumente nach -execund bis ;oder +zusammen mit den Argumenten des Befehls ausgeführt werden sollen, wobei jede Instanz eines {}Arguments durch die aktuelle Datei (mit ;) und {}als letztes Argument zuvor +durch eine Liste von Dateien ersetzt wird als separate Argumente (in dem {} +Fall). IOW -execnimmt mehrere Argumente, die von einem beendet ;oder {} +.
Stéphane Chazelas