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:
- 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
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:
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.
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 {} +