Comprensione dell'opzione -exec di `find`


53

Mi ritrovo a cercare costantemente la sintassi di

find . -name "FILENAME"  -exec rm {} \;

principalmente perché non vedo come funziona esattamente la -execparte. Qual è il significato delle parentesi graffe, della barra rovesciata e del punto e virgola? Ci sono altri casi d'uso per quella sintassi?


11
@Philippos: vedo il tuo punto. Si prega di tenere presente che le pagine man sono un riferimento, vale a dire utile per coloro che hanno una comprensione della questione per cercare la sintassi. Per chi non conosce l'argomento, sono spesso criptici e formali per essere utili. Scoprirai che la risposta accettata è circa 10 volte più lunga della voce della pagina man, e questo è per un motivo.
Zsolt Szilagy,

6
Anche la vecchia manpagina POSIX riporta A nome_utilità o argomento contenente solo i due caratteri "{}" deve essere sostituito dal percorso corrente , che mi sembra sufficiente. Inoltre ha un esempio con -exec rm {} \;, proprio come nella tua domanda. Ai miei tempi, c'erano a malapena altre risorse oltre al "grande muro grigio", libri di manpagine stampate (la carta era più economica della conservazione). Quindi so che questo è sufficiente per qualcuno di nuovo sull'argomento. La tua ultima domanda, tuttavia, è corretta da porre qui. Sfortunatamente né @Kusalananda né io abbiamo una risposta a questo.
Philippos,

1
Comeon @Philippos. Stai davvero dicendo a Kusalananda che non è migliorato sulla manpage? :-)
Zsolt Szilagy,

1
@allo Anche se a xargsvolte è utile, findpuò passare più argomenti di percorso per comandare senza di essa. -exec command... {} +(con +invece di \;) passa tutti i percorsi dopo command...che si adatteranno (ogni sistema operativo ha il suo limite su quanto può essere lunga una riga di comando). E come xargs, il +modulo -terminated di find's -execazione sarà anche eseguire command...più volte nel caso raro che ci sono troppi percorsi per adattarsi entro il limite.
Eliah Kagan,

2
@ZsoltSzilagy Non ho detto né voluto dire. Ti ha dato da mangiare molto bene, penso solo che sei abbastanza grande da mangiare da solo. (-;
Philippos,

Risposte:


90

Questa risposta arriva nelle seguenti parti:

  • Utilizzo di base di -exec
  • Usando -execin combinazione consh -c
  • utilizzando -exec ... {} +
  • utilizzando -execdir

Utilizzo di base di -exec

L' -execopzione prende un'utilità esterna con argomenti opzionali come argomento e la esegue.

Se la stringa {}è presente in qualsiasi punto del comando dato, ogni istanza di essa verrà sostituita dal percorso attualmente in elaborazione (ad es ./some/path/FILENAME.). Nella maggior parte delle shell, i due caratteri {}non devono essere citati.

Il comando deve essere terminato con un ;per findsapere dove finisce (poiché potrebbero esserci ulteriori opzioni in seguito). Per proteggere il ;dalla shell, deve essere citato come \;o ';', altrimenti la shell lo vedrà come la fine del findcomando.

Esempio ( \alla fine delle prime due righe sono solo per le continuazioni di riga):

find . -type f -name '*.txt'      \
   -exec grep -q 'hello' {} ';'   \
   -exec cat {} ';'

Questo troverà tutti i file regolari ( -type f) i cui nomi corrispondono al modello *.txtall'interno o sotto la directory corrente. Quindi verificherà se la stringa si helloverifica in uno dei file trovati usando grep -q(che non produce alcun output, solo uno stato di uscita). Per quei file che contengono la stringa, catverranno eseguiti per inviare il contenuto del file al terminale.

Ognuno -execagisce anche come un "test" sui percorsi trovati da find, proprio come -typee -namefa. Se il comando restituisce uno stato di uscita pari a zero (che significa "successo"), findviene considerata la parte successiva del comando, altrimenti il findcomando continua con il percorso successivo. Questo è usato nell'esempio sopra per trovare file che contengono la stringa hello, ma per ignorare tutti gli altri file.

L'esempio sopra illustra i due casi d'uso più comuni di -exec:

  1. Come test per limitare ulteriormente la ricerca.
  2. Per eseguire un qualche tipo di azione sul percorso trovato (di solito, ma non necessariamente, alla fine del findcomando).

Usando -execin combinazione consh -c

Il comando che -execpuò essere eseguito è limitato a un'utilità esterna con argomenti opzionali. L'uso diretto di shell incorporate, funzioni, condizionali, condutture, reindirizzamenti, ecc. Direttamente -execnon è possibile, a meno che non siano avvolti in qualcosa di simile a una sh -cshell figlio.

Se bashsono necessarie funzionalità, utilizzare bash -cal posto di sh -c.

sh -cviene eseguito /bin/shcon uno script fornito nella riga di comando, seguito da argomenti della riga di comando facoltativi per quello script.

Un semplice esempio di utilizzo sh -cda solo, senza find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

Questo passa due argomenti allo script della shell figlio:

  1. La stringa sh. Questo sarà disponibile come $0all'interno dello script e se la shell interna genera un messaggio di errore, lo precederà con questa stringa.

  2. L'argomento applesè disponibile come $1nello script, e se ci fossero stati più argomenti, questi sarebbero stati disponibili come $2, $3ecc. Sarebbero anche disponibili nell'elenco "$@"(tranne per il $0quale non farebbero parte "$@").

Ciò è utile in combinazione con in -execquanto ci consente di creare script arbitrariamente complessi che agiscono sui percorsi trovati da find.

Esempio: trova tutti i file regolari che hanno un certo suffisso per il nome del file e cambia quel suffisso per il nome del file in un altro suffisso, dove i suffissi vengono mantenuti nelle variabili:

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" {} ';'

All'interno dello script interno, ci $1sarebbe la stringa text, $2sarebbe la stringa txte $3sarebbe qualunque sia il percorso findtrovato per noi. L'espansione del parametro ${3%.$1}prenderebbe il percorso e rimuoverà il suffisso .textda esso.

Oppure, usando dirname/ basename:

find . -type f -name "*.$from" -exec sh -c '
    mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'

oppure, con l'aggiunta di variabili nello script interno:

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" {} ';'

Si noti che in quest'ultima variante, le variabili frome tonella shell figlio sono distinte dalle variabili con gli stessi nomi nello script esterno.

Quanto sopra è il modo corretto di chiamare uno script complesso arbitrario da -execcon find. Usando findin un ciclo come

for pathname in $( find ... ); do

è soggetto a errori e inelegante (opinione personale). Suddivide i nomi dei file negli spazi bianchi, invoca il globbing dei nomi dei file e forza anche la shell a espandere il risultato completo findprima ancora di eseguire la prima iterazione del ciclo.

Guarda anche:


utilizzando -exec ... {} +

Alla ;fine può essere sostituito da +. Ciò provoca findl'esecuzione del comando dato con il maggior numero possibile di argomenti (nomi di percorso trovati) anziché una volta per ogni nome di percorso trovato. La stringa {} deve essere presente poco prima +che funzioni .

find . -type f -name '*.txt' \
   -exec grep -q 'hello' {} ';' \
   -exec cat {} +

Qui, findraccoglierà i percorsi risultanti ed eseguirà catil maggior numero possibile di loro contemporaneamente.

find . -type f -name "*.txt" \
   -exec grep -q "hello" {} ';' \
   -exec mv -t /tmp/files_with_hello/ {} +

Allo stesso modo qui, mvverrà eseguito il minor numero di volte possibile. Questo ultimo esempio richiede GNU mvda coreutils (che supporta l' -topzione).

L'utilizzo -exec sh -c ... {} +è anche un modo efficace per eseguire il loop su una serie di percorsi con uno script arbitrariamente complesso.

Le basi sono le stesse dell'uso -exec sh -c ... {} ';', ma ora lo script richiede un elenco di argomenti molto più lungo. Questi possono essere ripetuti ciclicamente "$@"all'interno dello script.

Il nostro esempio dell'ultima sezione che modifica i suffissi del nome file:

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" {} +

utilizzando -execdir

C'è anche -execdir(implementato dalla maggior parte delle findvarianti, ma non un'opzione standard).

Funziona come -execcon la differenza che il comando shell dato viene eseguito con la directory del nome percorso trovato come directory di lavoro corrente e che {}conterrà il nome base del nome percorso trovato senza il suo percorso (ma GNU findcontinuerà comunque a precedere il nome base ./, mentre BSD findnon lo farà).

Esempio:

find . -type f -name '*.txt' \
    -execdir mv {} done-texts/{}.done \;

Ciò sposterà ogni *.txtfile trovato in una done-textssottodirectory preesistente nella stessa directory in cui è stato trovato il file . Il file verrà anche rinominato aggiungendo il suffisso .donead esso.

Questo sarebbe un po 'più complicato da fare -execpoiché dovremmo estrarre il nome di base del file trovato {}per formare il nuovo nome del file. È inoltre necessario il nome della directory da {}per individuare done-textscorrettamente la directory.

Con -execdir, alcune cose come queste diventano più facili.

L'operazione corrispondente usando -execinvece di -execdirdovrebbe impiegare una shell figlio:

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
    done' sh {} +

o,

find . -type f -name '*.txt' -exec sh -c '
    for name do
        mv "$name" "${name%/*}/done-texts/${name##*/}.done"
    done' sh {} +

7
-execaccetta un programma e argomenti e lo esegue; alcuni comandi di shell consistono solo di un programma e argomenti ma molti non lo fanno. Un comando shell può includere reindirizzamento e piping; -execimpossibile (sebbene l'intero findpossa essere reindirizzato). Un comando shell può usare ; && ifetc; -execnon posso, sebbene -a -opossa fare un po '. Un comando di shell può essere un alias o una funzione di shell o incorporato; -execnon può. Un comando di shell può espandere vari; -execimpossibile (sebbene la shell esterna che esegue la findlattina). Un comando shell può sostituire $(command)diversamente ogni volta; -execnon può. ...
dave_thompson_085,

... Un comando shell può glob, -execno - anche se findpuò iterare i file nello stesso modo in cui farebbe la maggior parte dei globs, quindi raramente lo si desidera.
dave_thompson_085,

@ dave_thompson_085 Naturalmente, il comando shell può essere shse stesso, il che è pienamente in grado di fare tutte queste cose
Tavian Barnes,

2
Dire che è un comando di shell qui è sbagliato, find -exec cmd arg \;non invoca una shell per interpretare una riga di comando di shell, viene eseguita execlp("cmd", "arg")direttamente, non execlp("sh", "-c", "cmd arg")(per cui la shell finirebbe per fare l'equivalente di execlp("cmd", "arg")se cmdnon fosse incorporata).
Stéphane Chazelas,

2
È possibile chiarire che tutti gli findargomenti dopo -exece fino a ;o +compongono il comando da eseguire insieme ai suoi argomenti, con ogni istanza di un {}argomento sostituita con il file corrente (con ;) e {}come ultimo argomento prima +sostituito con un elenco di file come argomenti separati (nel {} +caso). IOW -execaccetta diversi argomenti, terminati da un ;o {} +.
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.