verstehe, schaue ich ständig nach der Syntax von

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

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

Kommentare

  • @Philippos: Ich verstehe Ihren Standpunkt. Bitte denken Sie daran, dass die Manpages eine Referenz sind, dh nützlich für diese Mit einem Verständnis der Angelegenheit, um die Syntax nachzuschlagen. Für jemanden, der neu im Thema ist, sind sie oft zu kryptisch und formal, um nützlich zu sein. Sie werden feststellen, dass die akzeptierte Antwort ungefähr zehnmal so lang ist wie der Manpage-Eintrag und Diese ' hat einen Grund.
  • Sogar die alte POSIX man -Seite liest einen Dienstprogrammnamen oder ein Argument mit nur zwei Zeichen " {} " wird ersetzt d durch den aktuellen Pfadnamen , der mir ausreichend erscheint. Zusätzlich gibt es ein Beispiel mit -exec rm {} \;, genau wie in Ihrer Frage. In meinen Tagen gab es kaum andere Ressourcen als die große graue Wand " ", Bücher mit gedruckten man Seiten (Papier war billiger als Lagerung). Ich weiß also, dass dies für jemanden ausreicht, der neu im Thema ist. Ihre letzte Frage ist hier fair zu stellen. Leider haben weder @Kusalananda noch ich eine Antwort darauf.
  • @Philippos 🙂 Oh, das wäre eine aufregende Sache, um es zu versuchen, für eine Stufe von " aufregend ", das außerhalb der Skala von meinem Diagramm liegt.
  • Comeon @Philippos. Erzählst du Kusalananda wirklich, dass er die Manpage nicht verbessert hat? 🙂
  • @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. (-;

Antwort

Diese Antwort besteht aus folgenden Teilen:

  • Grundlegende Verwendung von -exec
  • Verwenden von -exec in Kombination mit sh -c
  • Verwenden von -exec ... {} +
  • Verwenden von -execdir

Grundlegende Verwendung von -exec

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

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

Der Befehl muss mit einem ; beendet werden, damit find weiß, wo es endet (da es danach möglicherweise weitere Optionen gibt s). Um die ; vor der Shell zu schützen, muss sie als \; oder ";" angegeben werden Andernfalls sieht die Shell es als das Ende des Befehls find.

Beispiel (die \ am Das Ende der ersten beiden Zeilen dient nur zur Fortsetzung der Zeilen):

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

Hier werden alle regulären Dateien gefunden (-type f) dessen Namen mit dem Muster *.txt im oder unter dem aktuellen Verzeichnis übereinstimmen. Anschließend wird geprüft, ob die Zeichenfolge hello in einer der gefundenen Dateien mit grep -q vorkommt (die keine Ausgabe erzeugt, sondern nur einen Exit Status). Für die Dateien, die die Zeichenfolge enthalten, wird cat ausgeführt, um den Inhalt der Datei an das Terminal auszugeben.

Jede -exec verhält sich auch wie ein „Test“ für die von find gefundenen Pfadnamen, genau wie -type und -name tut. Wenn der Befehl einen Exit-Status von Null zurückgibt (was „Erfolg“ bedeutet), wird der nächste Teil des Befehls find berücksichtigt, andernfalls der Befehl find Befehl wird mit dem nächsten Pfadnamen fortgesetzt. Dies wird im obigen Beispiel verwendet, um Dateien zu finden, die die Zeichenfolge hello enthalten, aber alle anderen Dateien zu ignorieren.

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

  1. Als Test, um die Suche weiter einzuschränken.
  2. Um eine Aktion für das Gefundene auszuführen Pfadname (normalerweise, aber nicht unbedingt am Ende des Befehls find).

Verwenden von -exec in Kombination mit sh -c

Der Befehl, den -exec ausführen kann, ist auf ein externes Dienstprogramm beschränkt mit optionalen Argumenten.Es ist nicht möglich, integrierte Shell-Funktionen, Funktionen, Bedingungen, Pipelines, Umleitungen usw. direkt mit -exec zu verwenden, es sei denn, sie sind in eine sh -c untergeordnete Shell.

Wenn bash Funktionen erforderlich sind, verwenden Sie bash -c anstelle von sh -c.

sh -c führt /bin/sh mit einem in der Befehlszeile angegebenen Skript aus. gefolgt von optionalen Befehlszeilenargumenten für dieses Skript.

Ein einfaches Beispiel für die Verwendung von sh -c allein ohne find :

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

Hiermit werden zwei Argumente an das untergeordnete Shell-Skript übergeben. Diese werden in $0 und $1 abgelegt, damit das Skript verwendet werden kann.

  1. The Zeichenfolge sh. Dies ist im Skript als $0 verfügbar. Wenn die interne Shell eine Fehlermeldung ausgibt, wird ihr diese Zeichenfolge vorangestellt.

  2. Das Argument apples ist als $1 im Skript verfügbar. Wären mehr Argumente vorhanden gewesen, wären diese als verfügbar gewesen $2, $3 usw. Sie wären auch in der Liste "$@" verfügbar (außer für $0, das nicht Teil von "$@") wäre.

Dies ist nützlich in Kombination mit -exec, da wir damit beliebig komplexe Skripte erstellen können, die auf die von find gefundenen Pfadnamen einwirken.

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 der Inte rnal script, $1 wäre die Zeichenfolge text, $2 wäre die Zeichenfolge txt und $3 wäre der Pfadname, den find für uns gefunden hat. Die Parametererweiterung ${3%.$1} würde den Pfadnamen übernehmen und das Suffix .text daraus 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 in der internes 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 in dieser letzten Variante die Variablen from und to in der untergeordneten Shell unterscheiden sich von den Variablen mit demselben Namen im externen Skript.

Das Obige ist die korrekte Methode zum Aufrufen eines beliebigen komplexen Skripts aus -exec mit find. Die Verwendung von find in 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 vollständige Ergebnis von find zu erweitern, bevor überhaupt die erste Iteration der Schleife ausgeführt wird.

Siehe auch:


Verwenden von -exec ... {} +

Die ; am Ende kann durch . Dies führt dazu, dass find den angegebenen Befehl mit so vielen Argumenten (gefundenen Pfadnamen) wie möglich ausführt und nicht einmal für jeden gefundenen Pfadnamen. Die Zeichenfolge {} muss unmittelbar vor der + stehen, damit dies funktioniert .

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

Hier sammelt find die resultierenden Pfadnamen und führt cat auf so viele von ihnen wie möglich gleichzeitig.

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

Ebenso wird hier mv ausgeführt als einige Male wie möglich. Für dieses letzte Beispiel ist GNU mv von coreutils erforderlich (dies unterstützt die Option -t).

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 dieselben wie bei Verwendung von -exec sh -c ... {} ";", aber das Skript benötigt jetzt eine viel längere Liste von Argumenten. Diese können durch Schleifen über "$@" innerhalb des Skripts wiederholt 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 von -execdir

Es gibt auch -execdir ( implementiert von den meisten find -Varianten, jedoch keine Standardoption).

Dies funktioniert wie -exec mit dem Unterschied, dass der angegebene Shell-Befehl mit dem Verzeichnis des gefundenen Pfadnamens als aktuellem Arbeitsverzeichnis ausgeführt wird und dass {} enthält den Basisnamen des gefundenen Pfadnamens ohne seinen Pfad (aber GNU find wird dem Basisnamen weiterhin ./, während BSD find dies nicht tut).

Beispiel:

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

Dadurch wird jede gefundene *.txt -Datei in ein bereits vorhandenes done-texts -Unterverzeichnis in dasselbe Verzeichnis verschoben, in dem sich die Datei befand gefunden . Die Datei wird auch umbenannt, indem das Suffix .done hinzugefügt wird.

Dies wäre mit -exec da wir den Basisnamen der gefundenen Datei aus {} abrufen müssten, um den neuen Namen der Datei zu bilden. Wir benötigen auch den Verzeichnisnamen von {}, um das Verzeichnis done-texts richtig zu finden.

Mit -execdir, einige Dinge wie diese werden einfacher.

Die entsprechende Operation mit -exec anstelle von -execdir mü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 {} + 

Kommentare

  • -exec nimmt 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. -exec kann nicht (obwohl die gesamte find umgeleitet werden kann). Ein Shell-Befehl kann ; && if etc verwenden. -exec kann nicht, obwohl -a -o einige kann. Ein Shell-Befehl kann ein Alias oder eine Shell-Funktion sein oder eingebaut sein. -exec kann nicht. Ein Shell-Befehl kann vars erweitern. -exec kann nicht (obwohl die äußere Hülle, in der find ausgeführt wird, dies kann). Ein Shell-Befehl kann $(command) jedes Mal anders ersetzen. -exec kann nicht. …
  • Wenn Sie ' sagen, ist ein Shell-Befehl hier falsch. find -exec cmd arg \; ' Ruft eine Shell nicht auf, um eine Shell-Befehlszeile zu interpretieren. Sie führt execlp("cmd", "arg") direkt aus, nicht execlp("sh", "-c", "cmd arg") (für die die Shell würde am Ende das Äquivalent von execlp("cmd", "arg") ausführen, wenn cmd nicht eingebaut wäre.
  • Sie könnten das alles klarstellen die Argumente find nach -exec und bis zu ; oder + bilden den Befehl, der zusammen mit seinen Argumenten ausgeführt werden soll, wobei jede Instanz eines {} -Arguments durch die aktuelle Datei ersetzt wird (mit ;) und {} als letztes Argument, bevor + durch eine Liste von Dateien als separate Argumente ersetzt wurde (in der {} + case). IOW -exec akzeptiert mehrere Argumente, die durch ein ; oder {} +.
  • @Kusalananda Würde ' Ihr letztes Beispiel nicht auch mit diesem einfacheren Befehl funktionieren: find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'?
  • @Atralb Ja, das hätte auch funktioniert und den gleichen Effekt wie der letzte Code gehabt, aber anstatt In einer Schleife führen Sie einmal pro gefundener Datei sowohl sh als auch mv für jede gefundene Datei aus Bei großen Dateimengen spürbar langsamer.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.