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 gedrucktenman
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 mitsh -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
:
- Als Test, um die Suche weiter einzuschränken.
- 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.
-
The Zeichenfolge
sh
. Dies ist im Skript als$0
verfügbar. Wenn die interne Shell eine Fehlermeldung ausgibt, wird ihr diese Zeichenfolge vorangestellt. -
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:
- Warum wird die Ausgabe von ' schlecht wiederholt?
- Ist es möglich, `find -exec sh -c` sicher zu verwenden?
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 gesamtefind
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 derfind
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ührtexeclp("cmd", "arg")
direkt aus, nichtexeclp("sh", "-c", "cmd arg")
(für die die Shell würde am Ende das Äquivalent vonexeclp("cmd", "arg")
ausführen, wenncmd
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 auchmv
für jede gefundene Datei aus Bei großen Dateimengen spürbar langsamer.