Finden Sie -exec eine Shell-Funktion unter Linux?

185

Gibt es eine Möglichkeit find, eine in der Shell definierte Funktion auszuführen? Beispielsweise:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

Das Ergebnis davon ist:

find: dosomething: No such file or directory

Gibt es eine Möglichkeit zu bekommen finds‘ -execzu sehen dosomething?

alxndr
quelle

Antworten:

262

Da nur die Shell weiß, wie Shell-Funktionen ausgeführt werden, müssen Sie eine Shell ausführen, um eine Funktion auszuführen. Sie müssen Ihre Funktion für den Export auch mit markieren export -f, sonst erbt die Subshell sie nicht:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
Adam Rosenfield
quelle
7
Sie schlugen mich. Übrigens können Sie die geschweiften Klammern in die Anführungszeichen setzen, anstatt sie zu verwenden $0.
Bis auf weiteres angehalten.
20
Wenn Sie es verwenden find . -exec bash -c 'dosomething "$0"' {} \;, werden Leerzeichen (und andere seltsame Zeichen) in Dateinamen behandelt ...
Gordon Davisson
3
@alxndr: Das wird bei Dateinamen mit doppelten Anführungszeichen, Backquotes, Dollarzeichen, einigen Fluchtkombinationen usw.
Gordon Davisson
4
Beachten Sie auch, dass alle Funktionen, die Ihre Funktion möglicherweise aufruft, nur verfügbar sind, wenn Sie diese ebenfalls exportieren.
Hraban
3
Das export -ffunktioniert nur in einigen Versionen von Bash. Es ist nicht posix, nicht crossplatforn, /bin/shwird einen Fehler damit haben
Роман Коптев
120
find . | while read file; do dosomething "$file"; done
Jac
quelle
10
Schöne Lösung. Erfordert kein Exportieren der Funktion oder Herumspielen von Escape-Argumenten und ist vermutlich effizienter, da keine Subshells erzeugt werden, um jede Funktion auszuführen.
Tom
19
Beachten Sie jedoch, dass Dateinamen mit Zeilenumbrüchen beschädigt werden.
Chepper
3
Dies ist eher "Shell'ish", da Ihre globalen Variablen und Funktionen verfügbar sind, ohne jedes Mal eine völlig neue Shell / Umgebung zu erstellen. Ich habe das auf die harte Tour gelernt, nachdem ich Adams Methode ausprobiert und auf alle möglichen Umgebungsprobleme gestoßen bin. Diese Methode beschädigt auch nicht die Shell Ihres aktuellen Benutzers bei allen Exporten und erfordert weniger Disziplin.
Ray Foss
SO VIEL SCHNELLER als akzeptierte Antwort ohne Sub-Shell! NETT! Danke dir!
Bill Heller
2
Außerdem habe ich mein Problem behoben, indem ich while readfür eine for-Schleife gewechselt habe . for item in $(find . ); do some_function "${item}"; done
user5359531
22

Jaks Antwort oben ist großartig, hat aber ein paar Fallstricke, die leicht zu überwinden sind:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

Dies verwendet null als Trennzeichen anstelle eines Zeilenvorschubs, sodass Dateinamen mit Zeilenvorschub funktionieren. Es wird auch das -rFlag verwendet, das das Escaping von Backslashs deaktiviert, ohne dass Backslashes in Dateinamen nicht funktionieren. Es wird auch gelöscht, IFSdamit potenzielle nachgestellte Leerzeichen in Namen nicht verworfen werden.

pajamian
quelle
1
Es ist gut für /bin/bash, wird aber nicht funktionieren /bin/sh. Wie schade.
17оман Коптев
@ РоманКоптев Wie glücklich, dass es zumindest in / bin / bash funktioniert.
Sdenham
21

Fügen Sie Anführungszeichen {}wie unten gezeigt hinzu:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

Dies korrigiert alle Fehler aufgrund von Sonderzeichen, die von zurückgegeben werden find, z. B. Dateien mit Klammern im Namen.

Wagner
quelle
1
Dies ist nicht die richtige Verwendung {}. Dies wird für einen Dateinamen mit doppelten Anführungszeichen unterbrochen. touch '"; rm -rf .; echo "I deleted all you files, haha. Hoppla.
gniourf_gniourf
Ja, das ist sehr schlecht. Es kann durch Injektionen ausgenutzt werden. Sehr unsicher. Verwenden Sie dies nicht!
Dominik
1
@kdubs: Verwenden Sie $ 0 (ohne Anführungszeichen) in der Befehlszeichenfolge und übergeben Sie den Dateinamen als erstes Argument: -exec bash -c 'echo $0' '{}' \;Beachten Sie, dass bei Verwendung bash -c$ 0 das erste Argument ist, nicht der Skriptname.
Sdenham
10

Verarbeitung führt zu Bulk

Um die Effizienz zu steigern, verarbeiten viele Menschen xargsErgebnisse in großen Mengen, dies ist jedoch sehr gefährlich. Aus diesem Grund wurde eine alternative Methode eingeführt find, mit der Ergebnisse in großen Mengen ausgeführt werden.

Beachten Sie jedoch, dass diese Methode möglicherweise mit einigen Einschränkungen verbunden ist, z. B. einer Anforderung in POSIX find, die {}am Ende des Befehls gelten muss.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findÜbergibt viele Ergebnisse als Argumente an einen einzelnen Aufruf von bashund die forSchleife durchläuft diese Argumente und führt die Funktion dosomethingfür jedes dieser Argumente aus.

Die obige Lösung beginnt mit Argumenten $1, weshalb es ein _(was darstellt $0) gibt.

Verarbeitungsergebnisse nacheinander

Ebenso denke ich, dass die akzeptierte Top-Antwort korrigiert werden sollte, um zu sein

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

Dies ist nicht nur vernünftiger, da Argumente immer bei beginnen sollten $1, sondern auch die Verwendung von $0kann zu unerwartetem Verhalten führen, wenn der von zurückgegebene Dateiname findeine besondere Bedeutung für die Shell hat.

Dominik
quelle
9

Lassen Sie das Skript sich selbst aufrufen und übergeben Sie jedes gefundene Element als Argument:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

Wenn Sie das Skript selbst ausführen, findet es, wonach Sie suchen, und ruft sich selbst auf, wobei jedes Suchergebnis als Argument übergeben wird. Wenn das Skript mit einem Argument ausgeführt wird, führt es die Befehle für das Argument aus und wird dann beendet.

Mike Maready
quelle
coole Idee, aber schlechter Stil: Verwendet dasselbe Skript für zwei Zwecke. Wenn Sie die Anzahl der Dateien in Ihrem Bin / reduzieren möchten, können Sie alle Ihre Skripte zu einem einzigen zusammenführen, das zu Beginn eine große case-Klausel enthält. sehr saubere Lösung, nicht wahr?
user829755
find: ‘myscript.sh’: No such file or directorybash myscript.sh
Ganz
5

Für diejenigen unter Ihnen, die nach einer Bash-Funktion suchen, die einen bestimmten Befehl für alle Dateien im aktuellen Verzeichnis ausführt, habe ich einen der folgenden Antworten zusammengestellt:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

Beachten Sie, dass es mit Dateinamen bricht, die Leerzeichen enthalten (siehe unten).

Nehmen Sie als Beispiel diese Funktion:

world(){
    sed -i 's_hello_world_g' "$1"
}

Angenommen, ich wollte alle Instanzen von Hallo in allen Dateien im aktuellen Verzeichnis in Welt ändern. Ich würde tun:

toall world

Verwenden Sie zur Sicherheit mit Symbolen in Dateinamen:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(aber du brauchst ein find, das -print0zB GNU handhabt find).

Jason Basanese
quelle
3

Ich finde den einfachsten Weg wie folgt: Ich wiederhole zwei Befehle in einem einzigen Schritt

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done
edib
quelle
3

Als Referenz vermeide ich dieses Szenario mit:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

Holen Sie sich die Ausgabe von find in der aktuellen Skriptdatei und durchlaufen Sie die Ausgabe nach Bedarf. Ich stimme der akzeptierten Antwort zu, möchte aber keine Funktion außerhalb meiner Skriptdatei verfügbar machen.

Dinesh Saini
quelle
Dies hat Größenbeschränkungen
Richard
2

Es ist nicht möglich, eine Funktion auf diese Weise auszuführen.

Um dies zu überwinden, können Sie Ihre Funktion in ein Shell-Skript einfügen und diese von aufrufen find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

Verwenden Sie es jetzt in find als:

find . -exec dosomething.sh {} \;
Codaddict
quelle
Ich habe versucht, mehr Dateien in meinem ~ / bin zu vermeiden. Trotzdem danke!
Alxndr
Ich habe über ein Downvoting nachgedacht, aber die Lösung an sich ist nicht schlecht. Bitte verwenden Sie einfach das richtige Zitat: dosomething $1=> dosomething "$1"und starten Sie Ihre Datei korrekt mitfind . -exec bash dosomething.sh {} \;
Camusensei
2

Legen Sie die Funktion in eine separate Datei und findführen Sie diese aus.

Shell-Funktionen befinden sich innerhalb der Shell, in der sie definiert sind. findwird sie niemals sehen können.

Angus
quelle
Erwischt; macht Sinn. Ich habe versucht, mehr Dateien in meinem ~ / bin zu vermeiden.
Alxndr
2

Wenn Sie die Bulk-Option für execoder execdir( -exec command {} +) verwenden und alle Positionsargumente abrufen möchten, um Ergänzungen und Erläuterungen zu einigen anderen Antworten bereitzustellen , müssen Sie die Behandlung $0mit berücksichtigen bash -c. Betrachten Sie genauer den folgenden Befehl, der bash -cwie oben vorgeschlagen verwendet wird, und geben Sie einfach Dateipfade aus, die mit '.wav' enden, aus jedem gefundenen Verzeichnis:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

Das Bash-Handbuch sagt:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

Hier 'check $@'ist die Befehlszeichenfolge und _ {}sind die Argumente nach der Befehlszeichenfolge. Beachten Sie, dass dies $@ein spezieller Positionsparameter in bash ist, der ab 1 auf alle Positionsparameter erweitert wird . Beachten Sie auch, dass bei dieser -cOption das erste Argument dem Positionsparameter zugewiesen wird $0. Das heißt, wenn Sie versuchen, auf alle Positionsparameter mit zuzugreifen $@, erhalten Sie nur Parameter ab $1und nach. Dies ist der Grund, warum Dominiks Antwort das _Dummy-Argument zum Füllen von Parametern enthält $0, sodass alle gewünschten Argumente später verfügbar sind, wenn wir beispielsweise die $@Parametererweiterung oder die for-Schleife wie in dieser Antwort verwenden.

Ähnlich wie bei der akzeptierten Antwort bash -c 'shell_function $0 $@'würde dies natürlich auch durch explizites Übergeben funktionieren $0, aber auch hier müssten Sie bedenken, dass dies $@nicht wie erwartet funktioniert.

Feuerwehrmann
quelle
0

Nicht direkt, nein. Die Suche wird in einem separaten Prozess ausgeführt, nicht in Ihrer Shell.

Erstellen Sie ein Shell-Skript, das den gleichen Job wie Ihre Funktion ausführt, und finden Sie -execdas.

Laurence Gonsalves
quelle
Ich habe versucht, mehr Dateien in meinem ~ / bin zu vermeiden. Trotzdem danke!
Alxndr
-2

Ich würde es ganz vermeiden -exec. Warum nicht verwenden xargs?

find . -name <script/command you're searching for> | xargs bash -c
Barry
quelle
Zu dieser Zeit versuchte das IIRC, die Menge der verwendeten Ressourcen zu reduzieren. Denken Sie daran, Millionen leerer Dateien zu finden und zu löschen.
Alxndr