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

  • @Philippos: Văd ideea dvs. Vă rugăm să rețineți că paginile de manual sunt o referință, adică utile pentru cele cu o înțelegere a problemei pentru a căuta sintaxa. Pentru cineva nou la acest subiect, acestea sunt adesea la criptă și formale pentru a fi utile. Veți găsi că răspunsul acceptat este de aproximativ 10 ori mai lung decât intrarea în pagina de manual și ' pentru un motiv.
  • Chiar și vechea pagină POSIX 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ărite man 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.
  • @Philippos 🙂 Oh, asta ar fi un lucru interesant de încercat, pentru un nivel de " interesant " care este în afara scalei diagramei my .
  • Comeon @Philippos. Îi spui cu adevărat lui Kusalananda că nu s-a îmbunătățit pe pagina de manual? 🙂
  • @ZsoltSzilagy N-am spus asta și nici nu am vrut să spun asta. El te-a hrănit foarte bine, cred că ești suficient de mare pentru a mânca singur. (-;

Răspuns

Acest răspuns vine în următoarele părți:

  • Utilizarea de bază a -exec
  • Utilizarea -exec în combinație cu sh -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:

  1. Ca test pentru a restricționa în continuare căutarea.
  2. 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.

  1. ș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.

  2. 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:


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 întregul find 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, nu execlp("sh", "-c", "cmd arg") (pentru care shell ar ajunge să facă echivalentul execlp("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ât sh cât și mv pentru fiecare fișier găsit, care va fi considerabil mai lent pentru cantități mari de fișiere.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *