Ausführen einer benutzerdefinierten Funktion in einem Aufruf von find -exec

25

Ich bin auf Solaris 10 und habe Folgendes mit ksh (88), bash (3.00) und zsh (4.2.1) getestet.

Der folgende Code liefert kein Ergebnis:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

Der Fund hat mehrere Dateien entspricht (wie durch Ersetzen gezeigt -exec ...mit -print), und der Funktion funktioniert perfekt , wenn außerhalb von dem gerufenen findAnruf.

Hier ist, was die man findSeite sagt -exec:

 -exec Befehl True, wenn der ausgeführte Befehl a zurückgibt
                     Nullwert als Ausgangsstatus. Das Ende von
                     Befehl muss durch ein Escapezeichen unterbrochen werden
                     Semikolon (;). Ein Befehlsargument {} ist
                     ersetzt durch den aktuellen Pfadnamen. Wenn das
                     Das letzte Argument für -exec ist {} und Sie
                     Geben Sie + anstelle des Semikolons (;) an.
                     Der Befehl wird mit weniger Male aufgerufen
                     {} ersetzt durch Gruppen von Pfadnamen. Ob
                     Jeder Aufruf des Befehls gibt a zurück
                     Nicht-Null-Wert als Exit-Status, finden
                     Gibt einen Exit-Status ungleich Null zurück.

Ich könnte wahrscheinlich mit etwas davonkommen:

for f in $(find somedir); do
    foo
done

Aber ich habe Angst, mich mit Feldtrennungsproblemen zu befassen.

Ist es möglich, eine Shell-Funktion (definiert im selben Skript, kümmern wir uns nicht um das Scoping) von einem find ... -exec ...Aufruf aus aufzurufen?

Ich habe es mit beiden /usr/bin/findund versucht /bin/findund habe das gleiche Ergebnis erzielt.

rahmu
quelle
Haben Sie versucht, die Funktion zu exportieren, nachdem Sie sie deklariert haben? export -f foo
h3rrmiller
2
Sie müssen die Funktion in ein externes Skript umwandeln und einfügen PATH. Alternativ können Sie mit sh -c '...'und beide definieren UND die Funktion im ...Bit ausführen . Es kann hilfreich sein, die Unterschiede zwischen Funktionen und Skripten zu verstehen .
Jw013

Antworten:

27

A functionist lokal für eine Shell, also brauchst du esfind -exec eine Shell erzeugen und diese Funktion in dieser Shell definieren, bevor Sie sie verwenden können. So etwas wie:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bash erlaubt es einem, Funktionen über die Umgebung mit zu exportieren export -f , so können Sie (in bash):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88 hat typeset -fx Funktion exportieren (nicht über die Umgebung), aber das kann nur von she-bang weniger Skripten ausgeführt werden ksh, also nicht mit ksh -c.

Eine andere Option ist zu tun:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

Verwenden Sie typeset -fdiese Option, um die Definition der fooFunktion im Inline-Skript zu sichern. Beachten Sie, dass Sie bei fooVerwendung anderer Funktionen diese ebenfalls sichern müssen.

Stéphane Chazelas
quelle
2
Können Sie erklären , warum es zwei Vorkommen sind kshoder bashin dem -execBefehl? Ich verstehe das erste, aber nicht das zweite Vorkommen.
Daniel Kullmann
6
@danielkullmann In bash -c 'some-code' a b c, $0ist a, $1ist b..., wenn Sie so wollen , $@um seine a, b, cSie benötigen , bevor etwas einzufügen. Da dies $0auch bei der Anzeige von Fehlermeldungen verwendet wird, ist es eine gute Idee, den Namen der Shell zu verwenden oder etwas, das in diesem Kontext sinnvoll ist.
Stéphane Chazelas
@ StéphaneChazelas, danke für die so nette Antwort.
User9102d82
5

Dies ist nicht immer anwendbar, aber wenn es so ist, ist es eine einfache Lösung. Stellen Sie die globstarOption ein ( set -o globstarin ksh93 shopt -s globstarin bash ≥4; in zsh ist sie standardmäßig aktiviert). Verwenden Sie dann **/, um das aktuelle Verzeichnis und seine Unterverzeichnisse rekursiv abzugleichen.

Beispielsweise können Sie statt find . -name '*.txt' -exec somecommand {} \;ausführen

for x in **/*.txt; do somecommand "$x"; done

Stattdessen find . -type d -exec somecommand {} \;kannst du rennen

for d in **/*/; do somecommand "$d"; done

Stattdessen find . -newer somefile -exec somecommand {} \;kannst du rennen

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

Wenn **/es bei Ihnen nicht funktioniert (weil Ihre Shell es nicht hat oder weil Sie eine findOption benötigen , die kein Shell-Analogon hat), definieren Sie die Funktion im find -execArgument .

Gilles 'SO - hör auf böse zu sein'
quelle
Ich kann anscheinend keine globstarOption für die von ksh88mir verwendete Version von ksh ( ) finden.
Rahmu
@rahmu In der Tat ist es neu in ksh93. Hat Solaris 10 nicht irgendwo ein ksh93?
Gilles 'SO - hör auf, böse zu sein'
Laut diesem Blog-Beitrag wurde ksh93 in Solaris 11 eingeführt, um sowohl die Bourne Shell als auch ksh88 zu ersetzen ...
rahmu 16.10.12
@rahmu. Solaris hat Ksh93 für eine Weile als dtksh(Ksh93 mit einigen X11-Erweiterungen), aber eine alte Version und möglicherweise Teil eines optionalen Pakets, in dem CDE optional ist.
Stéphane Chazelas
Es sollte beachtet werden, dass rekursives Globbing sich dadurch unterscheidet, finddass es Punktdateien ausschließt und nicht in Punktverzeichnisse abfällt und die Dateiliste sortiert (beides können Adressen in zsh durch Globbing-Qualifizierer sein). Auch mit **/*im Gegensatz zu ./**/*können Dateinamen mit beginnen -.
Stéphane Chazelas
4

Verwenden Sie \ 0 als Trennzeichen und lesen Sie die Dateinamen aus einem erzeugten Befehl in den aktuellen Prozess ein.

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

Was ist hier los:

  • read -d '' Liest bis zum nächsten \ 0 Byte, sodass Sie sich nicht um seltsame Zeichen in Dateinamen kümmern müssen.
  • Auf ähnliche Weise -print0wird \ 0 verwendet, um jeden generierten Dateinamen anstelle von \ n zu beenden.
  • cmd2 < <(cmd1)ist dasselbe wie mit der cmd1 | cmd2Ausnahme, dass cmd2 in der Haupt-Shell und nicht in einer Subshell ausgeführt wird.
  • Der Anruf an foo wird von umgeleitet, /dev/nullum sicherzustellen, dass er nicht versehentlich aus der Pipe gelesen wird.
  • $filename wird in Anführungszeichen gesetzt, damit die Shell nicht versucht, einen Dateinamen zu teilen, der Leerzeichen enthält.

Nun, read -dund <(...)sind in zsh, bash und ksh 93u, aber ich bin nicht sicher, was frühere ksh-Versionen angeht.

aecolley
quelle
4

Wenn Sie möchten, dass ein aus Ihrem Skript stammender untergeordneter Prozess eine vordefinierte Shell-Funktion verwendet, mit der Sie ihn exportieren müssen export -f <function>

HINWEIS: export -fist bash-spezifisch

da nur eine Shell Shell- Funktionen ausführen kann:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

BEARBEITEN: Im Wesentlichen sollte Ihr Skript so aussehen:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;
h3rrmiller
quelle
1
export -fist ein Syntaxfehler in kshund gibt die Funktionsdefinition auf dem Bildschirm in aus zsh. In allen ksh, zshund bash, löst es das Problem nicht.
Rahmu
1
find / -exec /bin/bash -c 'function' \;
h3rrmiller
Jetzt klappt es aber nur mit bash. Vielen Dank!
Rahmu
4
Niemals {}in den Shell-Code einbetten ! Das heißt, der Dateiname wird als Shell-Code interpretiert und ist daher sehr gefährlich
Stéphane Chazelas