Utilizzo di base di-exec
L' -exec
opzione 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 find
per 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 find
comando.
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 *.txt
nella directory corrente o al di sotto di essa. Quindi testerà se la stringa hello
si 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, cat
verrà eseguito per inviare in output il contenuto del file al terminale.
Ognuno -exec
agisce anche come un "test" sui pathname trovati da find
, proprio come fa -type
e -name
. Se il comando restituisce uno stato di uscita zero (che significa "successo"), find
viene 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
:
- Come test per restringere ulteriormente la ricerca.
- Per eseguire un'azione sul percorso trovato (solitamente, ma non necessariamente, alla fine del
find
comando).
Utilizzo -exec
in combinazione consh -c
Il comando che -exec
può essere eseguito è limitato a un'utilità esterna con argomenti opzionali. -exec
Non è possibile usare shell built-in, funzioni, condizionali, pipeline, reindirizzamenti ecc. direttamente con, a meno che non siano racchiusi in qualcosa come una sh -c
shell figlia.
Se bash
sono richieste delle funzionalità, utilizzare bash -c
al posto di sh -c
.
sh -c
viene eseguito /bin/sh
con uno script fornito sulla riga di comando, seguito da argomenti facoltativi della riga di comando per tale script.
Un semplice esempio di utilizzo sh -c
da 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 $0
e $1
per essere utilizzati dallo script.
-
La stringa
sh
. Sarà disponibile$0
all'interno dello script e, se la shell interna genera un messaggio di errore, lo anteporrà a questa stringa. -
L'argomento
apples
è disponibile come$1
nello script e, se ci fossero stati più argomenti, sarebbero stati disponibili come$2
,$3
ecc. Sarebbero stati disponibili anche nell'elenco"$@"
(tranne che per$0
which non farebbe parte di"$@"
).
Ciò è utile in combinazione con -exec
poiché 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, $1
ci sarebbe la stringa text
, $2
ci sarebbe la stringa txt
e $3
ci sarebbe qualsiasi pathname find
abbia trovato per noi. L'espansione dei parametri ${3%.$1}
prenderebbe il pathname e .text
ne 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 from
e to
nella 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 -exec
con find
. Usando find
in 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 find
prima 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ì find
che 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 find
verranno raccolti i percorsi risultanti ed eseguiti cat
quanti più possibili contemporaneamente.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
Allo stesso modo qui, mv
verrà eseguito il minor numero di volte possibile. Quest'ultimo esempio richiede GNU mv
da coreutils (che supporta l' -t
opzione).
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 find
delle 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 find
aggiungerà comunque il prefisso al nome base ./
, mentre BSD find
o sfind
no).
Esempio:
find . -type f -name '*.txt' \
-execdir mv -- {} 'done-texts/{}.done' \;
Questo sposterà ogni file trovato in una sottodirectory *.txt
preesistente 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)csh
find
{}
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-texts
correttamente la directory.
Con -execdir
, alcune cose come queste diventano più facili.
L'operazione corrispondente usando -exec
invece di -execdir
dovrebbe 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 {} +