Esecuzione della funzione definita dall'utente in una chiamata find -exec


25

Sono su Solaris 10 e ho testato quanto segue con ksh (88), bash (3.00) e zsh (4.2.1).

Il seguente codice non produce alcun risultato:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

La ricerca corrisponde a diversi file (come mostrato sostituendo -exec ...con -print) e la funzione funziona perfettamente quando viene chiamata all'esterno della findchiamata.

Ecco cosa man finddice la pagina su -exec:

 -exec command True se il comando eseguito restituisce a
                     valore zero come stato di uscita. La fine di
                     Il comando deve essere punteggiato da un escape
                     punto e virgola (;). Un argomento di comando {} è
                     sostituito dal percorso corrente. Se la
                     l'ultimo argomento di -exec è {} e tu
                     specificare + anziché il punto e virgola (;),
                     il comando viene richiamato meno volte, con
                     {} sostituito da gruppi di percorsi. Se
                     qualsiasi invocazione del comando restituisce a
                     valore diverso da zero come stato di uscita, trova
                     restituisce uno stato di uscita diverso da zero.

Probabilmente potrei andare via facendo qualcosa del genere:

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

Ma ho paura di affrontare i problemi di separazione dei campi.

È possibile chiamare una funzione shell (definita nello stesso script, non preoccupiamoci dei problemi di scoping) da una find ... -exec ...chiamata?

Ho provato sia con /usr/bin/finde /bin/finded ho ottenuto lo stesso risultato.


hai provato ad esportare la funzione dopo averla dichiarata? export -f foo
h3rrmiller,

2
Dovrai rendere la funzione uno script esterno e inserirla PATH. In alternativa, utilizzare sh -c '...'ed entrambi definire AND eseguire la funzione nel ...bit. Può aiutare a comprendere le differenze tra funzioni e script .
jw013

Risposte:


27

A functionè locale in una shell, quindi dovresti find -execgenerare una shell e avere quella funzione definita in quella shell prima di poterla usare. Qualcosa di simile a:

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

bashconsente di esportare le funzioni tramite l'ambiente export -f, in modo da poter fare (in bash):

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

ksh88deve typeset -fxesportare la funzione (non tramite l'ambiente), ma che può essere utilizzata solo da un numero minore di script eseguiti da ksh, quindi non con ksh -c.

Un'altra opzione è fare:

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

Cioè, utilizzare typeset -fper scaricare la definizione difoo funzione all'interno dello script inline. Nota che se foousi altre funzioni, dovrai anche scaricarle.


2
Puoi spiegare perché ci sono due occorrenze di ksho bashnel -execcomando? Capisco la prima, ma non la seconda occorrenza.
Daniel Kullmann,

6
@danielkullmann In bash -c 'some-code' a b c, $0è a, $1è b..., quindi se vuoi $@essere a, b, cdevi inserire qualcosa prima. Poiché $0viene utilizzato anche durante la visualizzazione di messaggi di errore, è consigliabile utilizzare il nome della shell o qualcosa di sensato in quel contesto.
Stéphane Chazelas,

@ StéphaneChazelas, grazie per la bella risposta.
Utente 9102d82

5

Questo non è sempre applicabile, ma quando lo è, è una soluzione semplice. Impostare l' globstaropzione ( set -o globstarin ksh93, shopt -s globstarin bash ≥4; è attivata per impostazione predefinita in zsh). Quindi utilizzare **/per abbinare ricorsivamente la directory corrente e le sue sottodirectory.

Ad esempio, invece di find . -name '*.txt' -exec somecommand {} \;, puoi eseguire

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

Invece di find . -type d -exec somecommand {} \;, puoi correre

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

Invece di find . -newer somefile -exec somecommand {} \;, puoi correre

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

Quando **/non funziona per te (perché la tua shell non ce l'ha o perché hai bisogno di findun'opzione che non ha un analogo della shell), definisci la funzione find -execnell'argomento .


Non riesco a trovare globstarun'opzione sulla versione di ksh ( ksh88) che sto usando.
Rahmu,

@rahmu In effetti, è nuovo in ksh93. Solaris 10 non ha un ksh93 da qualche parte?
Gilles 'SO- smetti di essere malvagio' il

Secondo questo post del blog ksh93 è stato introdotto in Solaris 11 per sostituire sia Bourne Shell che ksh88 ...
rahmu,

@rahmu. Solaris ha avuto ksh93 per un po 'di tempo dtksh(ksh93 con alcune estensioni X11), ma una versione precedente e forse parte di un pacchetto opzionale in cui CDE è opzionale.
Stéphane Chazelas il

Va notato che il globbing ricorsivo differisce dal fatto findche esclude dotfile e non scende in dotdir e che ordina l'elenco dei file (entrambi i quali possono essere indirizzi in zsh attraverso i qualificatori globbing). Inoltre, al **/*contrario ./**/*, i nomi dei file possono iniziare con -.
Stéphane Chazelas il

4

Usa \ 0 come delimitatore e leggi i nomi dei file nel processo corrente da un comando generato, in questo modo:

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

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

Cosa sta succedendo qui:

  • read -d '' legge fino al prossimo byte \ 0, quindi non devi preoccuparti di strani caratteri nei nomi dei file.
  • allo stesso modo, -print0 usa \ 0 per terminare ogni nome file generato invece di \ n.
  • cmd2 < <(cmd1) equivale a cmd1 | cmd2 tranne per il fatto che cmd2 viene eseguito nella shell principale e non in una subshell.
  • la chiamata a foo viene reindirizzata /dev/null per assicurarsi che non venga accidentalmente letta dalla pipe.
  • $filename viene citato in modo che la shell non tenti di dividere un nome file che contiene spazi bianchi.

Ora, read -de <(...)sono in zsh, bash e ksh 93u, ma non sono sicuro delle versioni precedenti di ksh.


4

se si desidera che un processo figlio, generato dallo script, utilizzi una funzione di shell predefinita con cui è necessario esportarlo export -f <function>

NOTA: export -fè specifico per bash

poiché solo una shell può eseguire funzioni shell :

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

EDIT: essenzialmente il tuo script dovrebbe assomigliare a questo:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

1
export -fè un errore di sintassi in kshe stampa la definizione della funzione sullo schermo in zsh. In tutti ksh, zshe bash, non risolve il problema.
Rahmu,

1
find / -exec /bin/bash -c 'function' \;
h3rrmiller,

Ora funziona, ma solo con bash. Grazie!
Rahmu,

4
Non inserire mai {}nel codice shell! Ciò significa che il nome del file viene interpretato come codice shell, quindi è molto pericoloso
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.