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 findcomando 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:
- Come test per restringere ulteriormente la ricerca.
- 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.
-
La stringa
sh. Sarà disponibile$0all'interno dello script e, se la shell interna genera un messaggio di errore, lo anteporrà a questa stringa. -
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:
- Perché eseguire un loop sull'output di find è una cattiva pratica?
- È possibile usare `find -exec sh -c` in modo sicuro?
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 {} +