Diese Antwort besteht aus folgenden Teilen:
- Grundsätzliche Verwendung von
-exec
- Verwendung
-exec
in Kombination mitsh -c
- Verwenden
-exec ... {} +
- Verwenden
-execdir
Grundsätzliche Verwendung von -exec
Die -exec
Option 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 find
zu 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 find
Befehls 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 *.txt
im oder unter dem aktuellen Verzeichnis übereinstimmen . Es wird dann geprüft, ob der String hello
in einer der gefundenen Dateien vorkommt grep -q
(was keine Ausgabe erzeugt, nur einen Exit-Status). Für diejenigen Dateien, die den String enthalten, cat
wird ausgeführt, um den Inhalt der Datei an das Terminal auszugeben.
Jeder -exec
verhält sich auch wie ein "Test" für die von gefundenen Pfadnamen find
, genau wie -type
und -name
. Wenn der Befehl einen Beendigungsstatus von Null zurückgibt (was "Erfolg" bedeutet), wird der nächste Teil des find
Befehls berücksichtigt, andernfalls wird der find
Befehl 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
:
- Als Test, um die Suche weiter einzuschränken.
- Ausführen einer Aktion für den gefundenen Pfadnamen (normalerweise, aber nicht unbedingt am Ende des
find
Befehls).
Verwendung -exec
in Kombination mitsh -c
Der Befehl, der ausgeführt werden -exec
kann, ist auf ein externes Dienstprogramm mit optionalen Argumenten beschränkt. Es -exec
ist nicht möglich, Shell-Built-Ins, Funktionen, Bedingungen, Pipelines, Umleitungen usw. direkt mit zu verwenden, es sei denn, sie sind in eine Art sh -c
Child-Shell eingebunden.
Wenn bash
Funktionen erforderlich sind, verwenden Sie bash -c
anstelle von sh -c
.
sh -c
Wird /bin/sh
mit 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 -c
ohne find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Dies übergibt zwei Argumente an das untergeordnete Shell-Skript:
Die Zeichenfolge sh
. Dies ist wie $0
im Skript verfügbar , und wenn die interne Shell eine Fehlermeldung ausgibt, wird dieser Zeichenfolge ein Präfix vorangestellt.
Das Argument apples
ist als verfügbar $1
im Drehbuch, und hatte es mehr Argumente gewesen, dann hätten diese als verfügbar gewesen $2
, $3
usw. Sie würden auch in der Liste vorhanden sein "$@"
(außer $0
denen nicht Teil sein würde "$@"
).
Dies ist in Kombination mit nützlich, -exec
da 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 $1
wäre das die Zeichenfolge text
, $2
wäre die Zeichenfolge txt
und $3
wäre der Pfadname find
, der für uns gefunden wurde. 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 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 from
und to
in der untergeordneten Shell von den gleichnamigen Variablen im externen Skript unterscheiden.
Das Obige ist die richtige Art, ein beliebiges komplexes Skript von -exec
mit aufzurufen find
. Verwenden Sie 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 gesamte Ergebnis zu erweitern, find
bevor sie überhaupt die erste Iteration der Schleife ausführt.
Siehe auch:
Verwenden -exec ... {} +
Das ;
am Ende darf durch ersetzt werden +
. Dies führt find
dazu, 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 find
werden die resultierenden Pfadnamen gesammelt und cat
auf 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 mv
so wenig wie möglich ausgeführt. Dieses letzte Beispiel erfordert GNU mv
von coreutils (was die -t
Option 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 find
Varianten implementiert , aber keine Standardoption).
Dies funktioniert -exec
mit 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 find
geht dem Basisnamen jedoch weiterhin voran ./
, während BSD aktiv ist find
werde 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-texts
Unterverzeichnis im selben Verzeichnis verschoben, in dem die Datei gefunden wurde . Die Datei wird auch umbenannt, indem das Suffix hinzugefügt .done
wird.
Dies wäre etwas kniffliger, -exec
da 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-texts
Verzeichnis richtig zu lokalisieren .
Mit -execdir
werden einige Dinge wie diese einfacher.
Die entsprechende Operation using -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 {} +