trova -exec una funzione shell in Linux?


185

C'è un modo per findeseguire una funzione che ho definito nella shell? Per esempio:

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

Il risultato è:

find: dosomething: No such file or directory

C'è un modo per ottenere find's -execvedere dosomething?

Risposte:


262

Poiché solo la shell sa come eseguire le funzioni della shell, è necessario eseguire una shell per eseguire una funzione. È inoltre necessario contrassegnare la funzione per l'esportazione con export -f, altrimenti la subshell non le erediterà:

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

7
Mi hai battuto. A proposito, puoi inserire le parentesi graffe tra virgolette invece di usare $0.
In pausa fino a nuovo avviso.

20
Se lo usi find . -exec bash -c 'dosomething "$0"' {} \;, gestirà gli spazi (e altri strani personaggi) nei nomi dei file ...
Gordon Davisson,

3
@alxndr: fallirà sui nomi di file con virgolette doppie, backquotes, simboli di dollaro, alcune combinazioni di escape, ecc ...
Gordon Davisson,

4
Nota anche che qualsiasi funzione chiamata dalla tua funzione non sarà disponibile se non esporti anche quelle.
Hraban,

3
L' export -ffunziona solo in alcune versioni di bash. Non è posix, non crossplatforn, /bin/shavrà un errore con esso
Роман Коптев

120
find . | while read file; do dosomething "$file"; done

10
Bella soluzione. Non richiede l'esportazione della funzione o il caos attorno agli argomenti di escape ed è presumibilmente più efficiente poiché non sta generando subshells per eseguire ciascuna funzione.
Tom,

19
Tieni presente, tuttavia, che si interromperà su nomi di file contenenti nuove righe.
Chepner,

3
Questo è più "shell'ish" in quanto le variabili e le funzioni globali saranno disponibili, senza creare ogni volta una shell / un ambiente completamente nuovi. L'ho imparato nel modo più duro dopo aver provato il metodo di Adam e aver incontrato tutti i tipi di problemi ambientali. Questo metodo inoltre non corrompe la shell dell'utente corrente con tutte le esportazioni e richiede meno dicipline.
Ray Foss,

COSÌ MOLTO PIÙ VELOCE della risposta accettata senza sub shell! SIMPATICO! Grazie!
Bill Heller,

2
inoltre ho risolto il mio problema cambiando while readper un ciclo for; for item in $(find . ); do some_function "${item}"; done
user5359531

22

La risposta di Jak sopra è ottima ma ha un paio di insidie ​​che possono essere facilmente superate:

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

Questo utilizza null come delimitatore invece di un avanzamento riga, quindi i nomi di file con avanzamenti riga funzioneranno. Utilizza anche il -rflag che disabilita la escape della barra rovesciata, senza le barre rovesciate nei nomi dei file non funzionerà. Si cancella anche in IFSmodo che i potenziali spazi bianchi finali nei nomi non vengano scartati.


1
Va bene /bin/bashma non funzionerà /bin/sh. Che peccato.
Роман Коптев,

@ РоманКоптев Che fortuna che almeno funzioni in / bin / bash.
sdenham,

21

Aggiungi le virgolette {}come mostrato di seguito:

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

Ciò corregge qualsiasi errore dovuto a caratteri speciali restituiti da find, ad esempio file con parentesi nel loro nome.


1
Questo non è il modo corretto di usare {}. Questo si interromperà per un nome file contenente virgolette doppie. touch '"; rm -rf .; echo "I deleted all you files, haha. Ops.
gniourf_gniourf,

Sì, questo è molto male. Può essere sfruttato per iniezioni. Molto pericoloso. Non usare questo!
Dominik,

1
@kdubs: usa $ 0 (non quotato) all'interno della stringa di comando e passa il nome del file come primo argomento: -exec bash -c 'echo $0' '{}' \;Nota che quando si usa bash -c, $ 0 è il primo argomento, non il nome dello script.
sdenham,

10

I risultati di elaborazione in blocco

Per una maggiore efficienza, molte persone usano xargsper elaborare i risultati in blocco, ma è molto pericoloso. Per questo motivo è stato introdotto un metodo alternativo findche esegue risultati in blocco.

Si noti tuttavia che questo metodo potrebbe presentare alcuni avvertimenti come ad esempio un requisito in POSIX- findda avere {}alla fine del comando.

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

findpasserà molti risultati come argomenti a una singola chiamata di bashe il for-loop scorre attraverso quegli argomenti, eseguendo la funzione dosomethingsu ognuno di questi.

La soluzione sopra inizia argomenti a $1, motivo per cui c'è un _(che rappresenta $0).

Elaborazione dei risultati uno per uno

Allo stesso modo, penso che la risposta migliore accettata debba essere corretta

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

Questo non è solo più sano, perché gli argomenti dovrebbero sempre iniziare da $1, ma anche l'uso $0potrebbe portare a comportamenti imprevisti se il nome file restituito findha un significato speciale per la shell.


9

Chiedi allo script di chiamarsi, passando ogni elemento trovato come argomento:

#!/bin/bash

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

find . -exec $0 {} \;

exit 0

Quando esegui lo script da solo, trova ciò che stai cercando e si chiama passando ogni risultato di ricerca come argomento. Quando lo script viene eseguito con un argomento, esegue i comandi sull'argomento e quindi esce.


bella idea ma cattivo stile: usa lo stesso copione per due scopi. se vuoi ridurre il numero di file nel tuo cestino / allora potresti unire tutti i tuoi script in uno solo con una clausola maiuscola all'inizio. soluzione molto pulita, vero?
user829755

per non parlare di questo fallirà find: ‘myscript.sh’: No such file or directoryse iniziato come bash myscript.sh...
Camusensei,

5

Per quelli di voi che cercano una funzione bash che eseguirà un determinato comando su tutti i file nella directory corrente, ne ho compilato uno dalle risposte sopra:

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

Si noti che si interrompe con i nomi di file contenenti spazi (vedere di seguito).

Ad esempio, prendi questa funzione:

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

Supponiamo di voler cambiare tutte le istanze di Hello in World in tutti i file nella directory corrente. Farei:

toall world

Per sicurezza con qualsiasi simbolo nei nomi dei file, utilizzare:

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

(ma hai bisogno di un findche gestisca -print0ad esempio GNU find).


3

Trovo il modo più semplice come segue, ripetendo due comandi in una sola operazione

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

3

Per riferimento, evito questo scenario usando:

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

Ottieni l'output di find nel file di script corrente e scorre l'output come desideri. Sono d'accordo con la risposta accettata, ma non voglio esporre la funzione al di fuori del mio file di script.


questo ha limiti di dimensione
Richard

2

Non è possibile eseguire una funzione in questo modo.

Per ovviare a questo, puoi inserire la tua funzione in uno script di shell e chiamarla da find

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

Ora usalo in trova come:

find . -exec dosomething.sh {} \;

Stavo cercando di evitare più file nel mio ~ / bin. Grazie comunque!
alxndr,

Ho considerato il downvoting ma la soluzione in sé non è male. Si prega di utilizzare solo la citazione corretta: dosomething $1=> dosomething "$1"e avviare correttamente il file confind . -exec bash dosomething.sh {} \;
Camusensei

2

Metti la funzione in un file separato e inizia finda eseguirla.

Le funzioni della shell sono interne alla shell in cui sono definite; findnon sarà mai in grado di vederli.


Gotcha; ha senso. Stavo cercando di evitare più file nel mio ~ / bin però.
alxndr,

2

Per fornire aggiunte e chiarimenti ad alcune delle altre risposte, se si utilizza l'opzione bulk per execo execdir( -exec command {} +) e si desidera recuperare tutti gli argomenti posizionali, è necessario considerare la gestione di $0with bash -c. Più concretamente, considera il comando seguente, che usa bash -ccome suggerito sopra, e fa semplicemente eco ai percorsi dei file che terminano con '.wav' da ogni directory che trova:

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

Il manuale di bash dice:

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.

Qui 'check $@'è la stringa di comando e _ {}sono gli argomenti dopo la stringa di comando. Si noti che $@è un parametro posizionale speciale in bash che si espande a tutti i parametri posizionali a partire da 1 . Si noti inoltre che con l' -copzione, il primo argomento viene assegnato al parametro posizionale $0. Ciò significa che se si tenta di accedere a tutti i parametri posizionali con $@, si otterranno solo parametri a partire da $1e verso l'alto. Questo è il motivo per cui la risposta di Dominik ha il _, che è un argomento fittizio per riempire il parametro $0, quindi tutti gli argomenti che vogliamo sono disponibili in seguito se usiamo l' $@espansione dei parametri per esempio, o il ciclo for come in quella risposta.

Naturalmente, simile alla risposta accettata, bash -c 'shell_function $0 $@'funzionerebbe anche passando esplicitamente $0, ma ancora una volta, dovresti tenere a mente che $@non funzionerà come previsto.


0

Non direttamente, no. Trova è in esecuzione in un processo separato, non nella shell.

Crea uno script di shell che fa lo stesso lavoro della tua funzione e trova -execche può farlo.


Stavo cercando di evitare più file nel mio ~ / bin. Grazie comunque!
alxndr,

-2

Eviterei di usare del -exectutto. Perché non usare xargs?

find . -name <script/command you're searching for> | xargs bash -c

All'epoca, IIRC era nel tentativo di ridurre la quantità di risorse utilizzate. Pensa di trovare milioni di file vuoti e di eliminarli.
alxndr,
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.