Mi ritrovo a cercare costantemente la sintassi di
find . -name "FILENAME" -exec rm {} \;
principalmente perché non vedo come funziona esattamente la parte -exec
. Qual è il significato delle parentesi graffe, della barra rovesciata e del punto e virgola? Esistono altri casi duso per che sintassi?
Commenti
Risposta
Questa risposta è suddivisa nelle seguenti parti:
- Utilizzo di base di
-exec
- Utilizzo di
-exec
in combinazione consh -c
- Utilizzo di
-exec ... {} +
- Utilizzo di
-execdir
Utilizzo di base di -exec
Lopzione -exec
accetta unutilità esterna con argomenti opzionali come il suo argomento e lo esegue.
Se la stringa {}
è presente ovunque nel comando dato, ogni sua istanza verrà sostituita dal percorso attualmente in fase di 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 ;
affinché find
sappia dove finisce (poiché potrebbero esserci ulteriori opzioni in seguito S). Per proteggere ;
dalla shell, deve essere citato come \;
o ";"
, altrimenti la shell lo vedrà come la fine del comando find
.
Esempio (il \
al la fine delle prime due righe è 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 pattern *.txt
nella o sotto la directory corrente. Quindi verificherà se la stringa hello
si verifica in uno dei file trovati utilizzando grep -q
(che non produce alcun output, solo unuscita stato). Per quei file che contengono la stringa, verrà eseguito cat
per inviare il contenuto del file al terminale.
Ogni -exec
agisce anche come un “test” sui nomi di percorso trovati da find
, proprio come -type
e -name
sì. Se il comando restituisce uno stato di uscita zero (che significa “successo”), viene considerata la parte successiva del comando find
, altrimenti find
il comando continua con il percorso successivo. Viene utilizzato nellesempio sopra per trovare file che contengono la stringa hello
, ma per ignorare tutti gli altri file.
Lesempio sopra illustra i due usi più comuni casi di -exec
:
- Come test per limitare ulteriormente la ricerca.
- Per eseguire un qualche tipo di azione sul trovato nome percorso (di solito, ma non necessariamente, alla fine del comando
find
).
Utilizzo di -exec
in combinazione con sh -c
Il comando che -exec
può eseguire è limitato a unutilità esterna con argomenti opzionali.Non è possibile utilizzare incorporati della shell, funzioni, condizionali, pipeline, reindirizzamenti ecc. Direttamente con -exec
, a meno che non sia racchiuso in qualcosa come sh -c
shell secondaria.
Se le funzionalità bash
sono richieste, utilizza bash -c
al posto di sh -c
.
sh -c
esegue /bin/sh
con uno script fornito dalla riga di comando, seguito da argomenti della riga di comando opzionali per lo script.
Un semplice esempio di utilizzo di 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. Questi verranno inseriti in $0
e $1
affinché lo script possa essere utilizzato.
-
Il stringa
sh
. Questo sarà disponibile come$0
allinterno dello script e se la shell interna restituisce un messaggio di errore, gli anteporrà questa stringa. -
Largomento
apples
è disponibile come$1
nello script e se ci fossero stati più argomenti, questi sarebbero stati disponibili come$2
,$3
ecc. Sarebbero anche disponibili nellelenco"$@"
(ad eccezione di$0
che non farebbe parte di"$@"
).
Questo è utile in combinazione con -exec
in quanto ci consente di creare script arbitrariamente complessi che agiscono sui nomi di percorso trovati da find
.
Esempio: trova tutti i file regolari che hanno un determinato suffisso del nome file e cambia il suffisso del nome file con un altro suffisso, dove i suffissi sono conservati 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" {} ";"
Dentro linte rnal script, $1
sarebbe la stringa text
, $2
sarebbe la stringa txt
e $3
sarebbe il percorso che find
ha trovato per noi. Lespansione del parametro ${3%.$1}
prenderebbe il percorso e rimuoverebbe il suffisso .text
da esso.
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 nel 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" {} ";"
Tieni presente che in questultima variante, le variabili from
e to
nella shell figlio sono distinte dalle variabili con lo stesso nome nello script esterno.
Quanto sopra è il modo corretto di chiamare uno script complesso arbitrario da -exec
con find
. Lutilizzo di find
in un ciclo come
for pathname in $( find ... ); do
è soggetto a errori e inelegante (opinione personale). Divide i nomi dei file su spazi bianchi, invoca il globbing dei nomi dei file e forza anche la shell ad espandere il risultato completo di find
prima ancora di eseguire la prima iterazione del ciclo.
Vedi anche:
- Perché il loop sulloutput di find ' è una cattiva pratica?
- È possibile usare `find -exec sh -c` in modo sicuro?
Utilizzo di -exec ... {} +
;
alla fine può essere sostituito da +
. Questo fa sì che find
esegua il comando dato con il maggior numero possibile di argomenti (nomi di percorso trovati) anziché una volta per ogni percorso trovato. La stringa {}
deve comparire appena prima di +
affinché funzioni .
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Qui, find
raccoglierà i nomi di percorso risultanti ed eseguirà cat
su più di essi possibile contemporaneamente.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} +
Allo stesso modo qui, mv
verrà eseguito come poche volte possibile. Questultimo esempio richiede GNU mv
di coreutils (che supporta lopzione -t
).
Usare -exec sh -c ... {} +
è anche un modo efficiente per eseguire il ciclo su un insieme di nomi di percorso con uno script arbitrariamente complesso.
Le basi sono le stesse di quando si utilizza -exec sh -c ... {} ";"
, ma lo script ora richiede un elenco di argomenti molto più lungo. Questi possono essere ripetuti eseguendo un ciclo su "$@"
allinterno dello script.
Il nostro esempio dallultima sezione che cambia i suffissi del nome del 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" {} +
Utilizzo di -execdir
Cè anche -execdir
( implementato dalla maggior parte delle find
varianti, ma non unopzione standard).
Funziona come -exec
con la differenza che il comando di 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
farà ancora anteporre al nome base ./
, mentre BSD find
non lo farà).
Esempio:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Questo sposterà ogni *.txt
-file trovato in una done-texts
sottodirectory preesistente nella stessa directory in cui si trovava il file trovato . Il file verrà anche rinominato aggiungendo il suffisso .done
.
Sarebbe un po più complicato da fare con -exec
poiché dovremmo ottenere il nome di base del file trovato da {}
per formare il nuovo nome del file. Abbiamo anche bisogno del nome della directory da {}
per individuare correttamente la directory done-texts
.
Con -execdir
, alcune cose come queste diventano più semplici.
Loperazione corrispondente utilizzando -exec
invece di -execdir
dovrebbe utilizzare una shell figlia:
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 {} +
Commenti
-
-exec
prende un programma e argomenti e lo esegue; alcuni comandi della shell consistono solo di un programma e di argomenti, ma molti no. Un comando di shell può includere reindirizzamento e piping;-exec
non può (sebbene linterofind
possa essere reindirizzato). Un comando di shell può utilizzare; && if
ecc;-exec
non può, anche se-a -o
può fare alcune cose. Un comando di shell può essere un alias, una funzione di shell o incorporato;-exec
non può. Un comando di shell può espandere vars;-exec
non può (anche se la shell esterna che eseguefind
può farlo). Un comando della shell può sostituire$(command)
in modo diverso ogni volta;-exec
non può. … - Dicendolo ' un comando della shell è sbagliato qui,
find -exec cmd arg \;
non ' t invoca una shell per interpretare una riga di comando della shell, esegueexeclp("cmd", "arg")
direttamente, nonexeclp("sh", "-c", "cmd arg")
(per cui il shell finirebbe per fare lequivalente diexeclp("cmd", "arg")
secmd
non fosse integrato). - Potresti chiarire che tutto gli
find
argomenti dopo-exec
e fino a;
o+
crea il comando da eseguire insieme ai suoi argomenti, con ogni istanza di un argomento{}
sostituita con il file corrente (con;
) e{}
come ultimo argomento prima di+
sostituito con un elenco di file come argomenti separati (nel{} +
case). IOW-exec
accetta diversi argomenti, terminati da un;
o{}
+
. - @Kusalananda ' Il tuo ultimo esempio funziona anche con questo comando più semplice:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Sì, anche questo avrebbe funzionato e avrebbe avuto lo stesso effetto dellultimo pezzo di codice, ma invece di eseguire
mv
in un ciclo, una volta per file trovato, esegui siash
emv
per ogni file trovato, che sarà notevolmente più lento per grandi quantità di file.
man
legge Un nome_utilità o un argomento contenente solo i due caratteri " {} " deve essere sostituito d dal percorso corrente , che mi sembra sufficiente. Inoltre ha un esempio con-exec rm {} \;
, proprio come nella tua domanda. Ai miei tempi, cerano a malapena altre risorse oltre al " grande muro grigio ", libri diman
pagine (la carta era più economica dello spazio di archiviazione). Quindi so che questo è sufficiente per qualcuno che non conosce largomento. La tua ultima domanda però è giusta da porre qui. Sfortunatamente né @Kusalananda né io abbiamo una risposta a questo.