Comprendere l'opzione -exec di `find`

Utilizzo di base di-exec

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

Se la stringa {}è presente in qualsiasi punto del comando dato, ogni sua istanza verrà sostituita dal pathname attualmente in elaborazione (ad esempio ./some/path/FILENAME). Nella maggior parte delle shell, i due caratteri {}non devono essere racchiusi tra virgolette.

Il comando deve essere terminato con un ;for findper sapere dove finisce (poiché potrebbero esserci altre opzioni in seguito). Per proteggere dalla ;shell, deve essere quotato come \;o ';', altrimenti la shell lo vedrà come la fine del findcomando.

Esempio (quelli \alla fine delle prime due righe servono solo per la continuazione delle righe):

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

Questo troverà tutti i file regolari ( -type f) i cui nomi corrispondono al pattern *.txtnella directory corrente o al di sotto di essa. Quindi testerà se la stringa hellosi verifica in uno qualsiasi dei file trovati usando grep -q(che non produce alcun output, solo uno stato di uscita). Per quei file che contengono la stringa, catverrà eseguito per inviare in output il contenuto del file al terminale.

Ognuno -execagisce anche come un "test" sui pathname trovati da find, proprio come fa -typee -name. Se il comando restituisce uno stato di uscita zero (che significa "successo"), findviene considerata la parte successiva del comando, altrimenti il find​​comando continua con il pathname successivo. Questo viene utilizzato nell'esempio precedente per trovare i file che contengono la stringa hello, ma per ignorare tutti gli altri file.

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

  1. Come test per restringere ulteriormente la ricerca.
  2. Per eseguire un'azione sul percorso trovato (solitamente, ma non necessariamente, alla fine del findcomando).

Utilizzo -execin combinazione consh -c

Il comando che -execpuò essere eseguito è limitato a un'utilità esterna con argomenti opzionali. -execNon è possibile usare shell built-in, funzioni, condizionali, pipeline, reindirizzamenti ecc. direttamente con, a meno che non siano racchiusi in qualcosa come una sh -cshell figlia.

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

sh -cviene eseguito /bin/shcon uno script fornito sulla riga di comando, seguito da argomenti facoltativi della riga di comando per tale 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 shell figlio. Questi saranno inseriti in $0e $1per essere utilizzati dallo script.

  1. La stringa sh. Sarà disponibile $0all'interno dello script e, se la shell interna genera un messaggio di errore, lo anteporrà a questa stringa.

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

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

Esempio: trova tutti i file normali che hanno un certo suffisso del nome file e modifica quel suffisso del nome file con 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, $1ci sarebbe la stringa text, $2ci sarebbe la stringa txte $3ci sarebbe qualsiasi pathname findabbia trovato per noi. L'espansione dei parametri ${3%.$1}prenderebbe il pathname e .textne rimuoverebbe il suffisso.

Oppure, utilizzando dirname/ basename:

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

oppure, con variabili aggiunte 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 figlia 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 poco elegante (opinione personale). Divide i nomi dei file in spazi vuoti, invoca il globbing dei nomi dei file e inoltre forza la shell a espandere il risultato completo di findprima ancora di eseguire la prima iterazione del ciclo.

Vedi anche:


Utilizzando-exec ... {} +

Il ;alla fine può essere sostituito da +. Ciò fa sì findche il comando specificato venga eseguito con quanti più argomenti (nomi di percorso trovati) possibili anziché una volta per ogni nome di percorso trovato. La stringa {} deve comparire subito prima di +affinché funzioni .

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

Qui findverranno raccolti i percorsi risultanti ed eseguiti catquanti più possibili 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. Quest'ultimo esempio richiede GNU mvda coreutils (che supporta l' -topzione).

L'utilizzo -exec sh -c ... {} +di è anche un modo efficiente per eseguire un ciclo su un insieme di percorsi con uno script arbitrariamente complesso.

Le basi sono le stesse di quando si usa -exec sh -c ... {} ';', ma lo script ora accetta un elenco di argomenti molto più lungo. Questi possono essere ripetuti ripetendo il ciclo "$@"all'interno dello script.

Il nostro esempio dall'ultima sezione che modifica i suffissi dei nomi dei 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

Esiste anche -execdir(implementato dalla maggior parte finddelle varianti, ma non è un'opzione standard).

Funziona così -exec, con la differenza che il comando shell specificato viene eseguito con la directory del percorso trovato come directory di lavoro corrente e che {}conterrà il nome base del percorso trovato senza il suo percorso (ma GNU findaggiungerà comunque il prefisso al nome base ./, mentre BSD findo sfindno).

Esempio:

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

Questo sposterà ogni file trovato in una sottodirectory *.txtpreesistente nella stessa directory in cui è stato trovato il file. Il file verrà anche rinominato aggiungendovi il suffisso . , per contrassegnare la fine delle opzioni è necessario qui in quelle implementazioni che non premettono il nome base con . Le virgolette attorno all'argomento che contiene not come un intero sono necessarie se la tua shell è . Nota anche che non tutte le implementazioni espanderanno quella lì ( non lo faranno).done-texts.done--find./{}(t)cshfind{}sfind

Questo sarebbe un po' più complicato da fare -exec, perché dovremmo ottenere il nome base del file trovato da {}per formare il nuovo nome del file. Abbiamo anche bisogno del 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 {} +
 

 

Ultimo aggiornamento 19.10.2024 08:12:14
index FAQ