Questa risposta arriva nelle seguenti parti:
- Utilizzo di base di
-exec
- Usando
-exec
in combinazione consh -c
- utilizzando
-exec ... {} +
- utilizzando
-execdir
Utilizzo di base di -exec
L' -exec
opzione 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 find
sapere 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 find
comando.
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 *.txt
all'interno o sotto la directory corrente. Quindi verificherà se la stringa si hello
verifica 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, cat
verranno eseguiti per inviare il contenuto del file al terminale.
Ognuno -exec
agisce anche come un "test" sui percorsi trovati da find
, proprio come -type
e -name
fa. Se il comando restituisce uno stato di uscita pari a zero (che significa "successo"), find
viene considerata la parte successiva del comando, altrimenti il find
comando 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
:
- Come test per limitare ulteriormente la ricerca.
- Per eseguire un qualche tipo di azione sul percorso trovato (di solito, ma non necessariamente, alla fine del
find
comando).
Usando -exec
in combinazione consh -c
Il comando che -exec
può essere eseguito è limitato a un'utilità esterna con argomenti opzionali. L'uso diretto di shell incorporate, funzioni, condizionali, condutture, reindirizzamenti, ecc. Direttamente -exec
non è possibile, a meno che non siano avvolti in qualcosa di simile a una sh -c
shell figlio.
Se bash
sono necessarie funzionalità, utilizzare bash -c
al posto di sh -c
.
sh -c
viene eseguito /bin/sh
con uno script fornito nella riga di comando, seguito da argomenti della riga di comando facoltativi per quello 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 della shell figlio:
La stringa sh
. Questo sarà disponibile come $0
all'interno dello script e se la shell interna genera un messaggio di errore, lo precederà con questa stringa.
L'argomento apples
è disponibile come $1
nello script, e se ci fossero stati più argomenti, questi sarebbero stati disponibili come $2
, $3
ecc. Sarebbero anche disponibili nell'elenco "$@"
(tranne per il $0
quale non farebbero parte "$@"
).
Ciò è utile in combinazione con in -exec
quanto 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 $1
sarebbe la stringa text
, $2
sarebbe la stringa txt
e $3
sarebbe qualunque sia il percorso find
trovato per noi. L'espansione del parametro ${3%.$1}
prenderebbe il percorso e rimuoverà il suffisso .text
da 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 from
e to
nella 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 -exec
con find
. Usando find
in 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 find
prima ancora di eseguire la prima iterazione del ciclo.
Guarda anche:
utilizzando -exec ... {} +
Alla ;
fine può essere sostituito da +
. Ciò provoca find
l'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, find
raccoglierà i percorsi risultanti ed eseguirà cat
il 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, mv
verrà eseguito il minor numero di volte possibile. Questo ultimo esempio richiede GNU mv
da coreutils (che supporta l' -t
opzione).
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 find
varianti, ma non un'opzione standard).
Funziona come -exec
con 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 find
continuerà comunque a precedere il nome base ./
, mentre BSD find
non lo farà).
Esempio:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Ciò sposterà ogni *.txt
file trovato in una done-texts
sottodirectory preesistente nella stessa directory in cui è stato trovato il file . Il file verrà anche rinominato aggiungendo il suffisso .done
ad esso.
Questo sarebbe un po 'più complicato da fare -exec
poiché 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-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 {} +