Mă trezesc permanent căutând sintaxa
find . -name "FILENAME" -exec rm {} \;
în principal pentru că nu văd cum funcționează exact partea -exec
. Care este semnificația acoladelor, a barei oblice și a punctului și virgula? Există alte cazuri de utilizare pentru această sintaxă?
Comentarii
Răspuns
Acest răspuns vine în următoarele părți:
- Utilizarea de bază a
-exec
- Utilizarea
-exec
în combinație cush -c
- Utilizarea
-exec ... {} +
- Utilizarea
-execdir
Utilizarea de bază a -exec
Opțiunea -exec
ia un utilitar extern cu argumente opționale ca argumentul său și îl execută.
Dacă șirul {}
este prezent oriunde în comanda dată, fiecare instanță a acestuia va fi înlocuită de calea care este în curs de procesare ( de ex. ./some/path/FILENAME
). În majoritatea shell-urilor, cele două caractere {}
nu trebuie să fie citate.
Comanda trebuie terminat cu un ;
pentru ca find
să știe unde se termină (deoarece pot exista și alte opțiuni ulterior s). Pentru a proteja ;
de shell, trebuie să fie citat ca \;
sau ";"
, în caz contrar, shell-ul îl va vedea ca sfârșitul comenzii find
.
Exemplu (\
la sfârșitul primelor două linii sunt doar pentru continuări de linie):
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";"
Acesta va găsi toate fișierele obișnuite (-type f
) ale cărui nume se potrivesc cu modelul *.txt
în sau sub directorul curent. Apoi va testa dacă șirul hello
apare în oricare dintre fișierele găsite folosind grep -q
(care nu produce nicio ieșire, doar o ieșire stare). Pentru acele fișiere care conțin șirul, cat
va fi executat pentru a transmite conținutul fișierului către terminal.
Fiecare -exec
acționează, de asemenea, ca un „test” pe căile identificate de find
, la fel ca -type
și -name
face. Dacă comanda returnează o stare de ieșire zero (ceea ce înseamnă „succes”), se ia în considerare următoarea parte a comenzii find
, în caz contrar find
comanda continuă cu următorul nume de cale. Aceasta este utilizată în exemplul de mai sus pentru a găsi fișiere care conțin șirul hello
, dar pentru a ignora toate celelalte fișiere.
Exemplul de mai sus ilustrează cele mai frecvente două utilizări cazuri de -exec
:
- Ca test pentru a restricționa în continuare căutarea.
- Pentru a efectua un fel de acțiune asupra celor găsite calea (de obicei, dar nu neapărat, la sfârșitul comenzii
find
).
Utilizarea -exec
în combinație cu sh -c
Comanda pe care -exec
o poate executa este limitată la un utilitar extern cu argumente opționale.Pentru a utiliza shell-uri încorporate, funcții, condiționare, conducte, redirecționări etc. direct cu -exec
nu este posibilă, cu excepția cazului în care este împachetat în ceva de genul sh -c
shell copil.
Dacă sunt necesare caracteristici bash
, utilizați bash -c
în locul sh -c
.
sh -c
rulează /bin/sh
cu un script dat pe linia de comandă, urmat de argumente opționale din linia de comandă pentru acel script.
Un exemplu simplu de utilizare a sh -c
de la sine, fără find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Aceasta transmite două argumente scriptului shell copil. Acestea vor fi plasate în $0
și $1
pentru ca scriptul să fie utilizat.
-
șir
sh
. Acest lucru va fi disponibil ca$0
în interiorul scriptului și, dacă shell-ul intern afișează un mesaj de eroare, îl va prefixa cu acest șir. -
Argumentul
apples
este disponibil ca$1
în script și dacă ar fi existat mai multe argumente, atunci acestea ar fi fost disponibile ca$2
,$3
etc. Acestea ar fi, de asemenea, disponibile în listă"$@"
(cu excepția$0
care nu ar face parte din"$@"
).
Acest lucru este util în combinație cu -exec
, deoarece ne permite să realizăm scripturi complexe în mod arbitrar, care acționează pe numele de cale găsite de find
.
Exemplu: Găsiți toate fișierele obișnuite care au un anumit sufix de nume de fișier și schimbați acel sufix de nume de fișier cu un alt sufix, unde sufixele sunt păstrate în variabile:
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" {} ";"
În interiorul inte script rnal, $1
ar fi șirul text
, $2
ar fi șirul txt
și $3
ar fi orice cale find
a găsit pentru noi. Extinderea parametrului ${3%.$1}
ar lua calea și ar elimina sufixul .text
din acesta.
Sau, folosind dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c " mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"" sh "$from" "$to" {} ";"
sau, cu variabile adăugate în script intern:
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" {} ";"
Rețineți că în această ultimă variantă, variabilele from
și to
din shell-ul copilului sunt distincte de variabilele cu aceleași nume din scriptul extern.
Mai sus este modul corect de a apela un script complex arbitrar de la -exec
cu find
. Utilizarea find
într-o buclă ca
for pathname in $( find ... ); do
este predispusă la erori și este inelegantă (opinie personală). Împarte numele fișierelor pe spații albe, invocă denumirea fișierelor și, de asemenea, forțează shell-ul să extindă rezultatul complet al find
înainte de a rula chiar prima iterație a buclei.
A se vedea, de asemenea:
- De ce este o practică proastă a rezultatelor ' de ieșire în buclă?
- Este posibil să folosiți `find -exec sh -c` în siguranță?
Utilizarea -exec ... {} +
;
la sfârșit poate fi înlocuit cu +
. Aceasta face ca find
să execute comanda dată cu cât mai multe argumente (nume de cale găsite) posibil, mai degrabă decât o dată pentru fiecare cale găsită. Șirul {}
trebuie să apară chiar înainte de +
pentru ca acest lucru să funcționeze .
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Aici, find
va colecta numele de cale rezultate și va executa cat
pe cât mai multe dintre ele posibil simultan.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} +
La fel și aici, mv
va fi executat ca de câteva ori posibil. Acest ultim exemplu necesită GNU mv
de la coreutils (care acceptă opțiunea -t
).
Utilizarea -exec sh -c ... {} +
este, de asemenea, o modalitate eficientă de a parcurge un set de căi cu un script arbitrar complex.
Noțiunile de bază sunt aceleași ca atunci când se utilizează -exec sh -c ... {} ";"
, dar scriptul ia acum o listă de argumente mult mai lungă. Acestea pot fi buclate prin buclă peste "$@"
în interiorul scriptului.
Exemplul nostru din ultima secțiune care modifică sufixele numelui de fișier:
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" {} +
Utilizarea -execdir
Există și -execdir
( implementat de majoritatea find
variante, dar nu o opțiune standard).
Acest lucru funcționează ca -exec
cu diferența că comanda de shell dată este executată cu directorul căii de acces găsit ca director de lucru curent și că {}
va conține numele de bază al căii găsite fără calea sa (dar GNU find
va prefixa în continuare numele de bază cu ./
, în timp ce BSD find
nu va face asta).
Exemplu:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Aceasta va muta fiecare fișier *.txt
găsit într-un subdirector done-texts
preexistent în același director în care era fișierul găsit . Fișierul va fi, de asemenea, redenumit adăugând sufixul .done
la acesta.
Acest lucru ar fi puțin mai complicat de făcut cu -exec
deoarece ar trebui să obținem numele de bază al fișierului găsit din {}
pentru a forma noul nume al fișierului. De asemenea, avem nevoie de numele directorului din {}
pentru a localiza corect directorul done-texts
.
Cu -execdir
, unele lucruri ca acestea devin mai ușoare.
Operațiunea corespunzătoare utilizând -exec
în loc de -execdir
ar trebui să folosească un shell copil:
find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done" done" sh {} +
sau,
find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "${name%/*}/done-texts/${name##*/}.done" done" sh {} +
Comentarii
-
-exec
preia un program și argumente și îl rulează; unele comenzi shell constau doar dintr-un program și argumente, dar multe nu. O comandă shell poate include redirecționarea și canalizarea;-exec
nu poate (deși întregulfind
poate fi redirecționat). O comandă shell poate utiliza; && if
etc;-exec
nu poate, deși-a -o
poate face unele. O comandă de shell poate fi un alias sau o funcție de shell sau integrată;-exec
nu poate. O comandă shell poate extinde var-urile;-exec
nu poate (deși shell-ul exterior care ruleazăfind
poate). O comandă shell poate înlocui$(command)
diferit de fiecare dată;-exec
nu poate. … - Dacă o spui ' comanda shell este greșită aici,
find -exec cmd arg \;
nu ' t invoca un shell pentru a interpreta o linie de comandă shell, ruleazăexeclp("cmd", "arg")
direct, nuexeclp("sh", "-c", "cmd arg")
(pentru care shell ar ajunge să facă echivalentulexeclp("cmd", "arg")
dacăcmd
nu ar fi fost încorporat). - Ați putea clarifica că toate argumentele
find
după-exec
și până la;
sau+
compuneți comanda de executat împreună cu argumentele sale, fiecare instanță a unui argument{}
înlocuită cu fișierul curent (cu;
) și{}
ca ultim argument înainte de+
înlocuit cu o listă de fișiere ca argumente separate (în{} +
caz). IOW-exec
ia mai multe argumente, terminate de un;
sau{}
+
. - @Kusalananda ' nu ar funcționa și cu acest exemplu mai simplu cu această comandă mai simplă:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Da, ar fi funcționat și ar avea același efect ca ultima bucată de cod, dar în loc să ruleze
mv
într-o buclă, o dată pe fișier găsit, executați atâtsh
cât șimv
pentru fiecare fișier găsit, care va fi considerabil mai lent pentru cantități mari de fișiere.
man
citește Un nume_utilitar sau un argument care conține doar cele două caractere " {} " se înlocuiește d de calea actuală , care mi se pare suficientă. În plus, are un exemplu cu-exec rm {} \;
, la fel ca în întrebarea dvs. În zilele mele, abia mai existau alte resurse decât " peretele mare și gri ", cărți de tipăriteman
pagini (hârtia era mai mică decât depozitarea). Deci știu că acest lucru este suficient pentru cineva nou la acest subiect. Ultima dvs. întrebare este totuși corectă de pus aici. Din păcate, nici @Kusalananda și nici eu nu avem un răspuns la asta.