Ik merk dat ik constant de syntaxis van

find . -name "FILENAME" -exec rm {} \; 

voornamelijk omdat ik niet zie hoe het -exec -gedeelte precies werkt. Wat is de betekenis van de accolades, de backslash en de puntkomma? Zijn er andere use-cases voor die syntaxis?

Opmerkingen

  • @Philippos: Ik begrijp je punt. Houd er rekening mee dat de man-paginas een referentie zijn, dwz nuttig voor degenen met begrip van de kwestie om de syntaxis op te zoeken. Voor iemand die nieuw is bij het onderwerp, zijn ze vaak te cryptisch en formeel om bruikbaar te zijn. U zult zien dat het geaccepteerde antwoord ongeveer 10 keer zo lang is als het manpage-item, en dat ' is met een reden.
  • Zelfs de oude POSIX man pagina leest Een utility_name of argument met alleen de twee tekens " {} " zal worden vervangen d door de huidige padnaam , wat mij voldoende lijkt. Bovendien heeft het een voorbeeld met -exec rm {} \;, net als in uw vraag. In mijn tijd waren er nauwelijks andere bronnen dan de " grote grijze muur ", gedrukte boeken man paginas (papier was goedkoper dan opslag). Dus ik weet dat dit voldoende is voor iemand die nieuw is bij het onderwerp. Uw laatste vraag is echter eerlijk om hier te stellen. Helaas hebben @Kusalananda noch ikzelf daar een antwoord op.
  • @Philippos 🙂 Oh, dat zou een opwindend iets zijn om te proberen, voor een niveau van " opwindend " die niet overeenkomt met de schaal van mijn kaart.
  • Kom op @Philippos. Vertel je Kusalananda echt dat hij de manpage niet heeft verbeterd? 🙂
  • @ZsoltSzilagy Dat heb ik niet verteld en ook niet bedoeld. Hij heeft je heel goed te eten gegeven, ik denk gewoon dat je oud genoeg bent om alleen te eten. (-;

Answer

Dit antwoord bestaat uit de volgende delen:

  • Basisgebruik van -exec
  • Gebruik van -exec in combinatie met sh -c
  • Met -exec ... {} +
  • Met -execdir

Basisgebruik van -exec

De -exec optie heeft een extern hulpprogramma met optionele argumenten als zijn argument en voert het uit.

Als de string {} ergens in het gegeven commando aanwezig is, zal elke instantie ervan worden vervangen door de padnaam die momenteel wordt verwerkt ( bijv. ./some/path/FILENAME). In de meeste shells hoeven de twee karakters {} niet te worden geciteerd.

Het commando moet worden beëindigd met een ; zodat find weet waar het eindigt (aangezien er daarna mogelijk meer opties zijn s). Om de ; tegen de shell te beschermen, moet deze worden geciteerd als \; of ";" , anders ziet de shell het als het einde van het find commando.

Voorbeeld (de \ aan de einde van de eerste twee regels zijn alleen voor lijnvervolgingen):

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";" 

Dit zal alle reguliere bestanden vinden (-type f) waarvan de naam overeenkomt met het patroon *.txt in of onder de huidige directory. Vervolgens wordt getest of de tekenreeks hello voorkomt in een van de gevonden bestanden met grep -q (die geen uitvoer produceert, alleen een exit toestand). Voor de bestanden die de string bevatten, wordt cat uitgevoerd om de inhoud van het bestand naar de terminal te sturen.

Elke -exec werkt ook als een “test” op de padnamen die zijn gevonden door find, net als -type en -name doet. Als de opdracht een exitstatus nul retourneert (wat “succes” betekent), wordt het volgende deel van de find -opdracht beschouwd, anders de find commando gaat verder met de volgende padnaam. Dit wordt in het bovenstaande voorbeeld gebruikt om bestanden te vinden die de tekenreeks hello bevatten, maar om alle andere bestanden te negeren.

Het bovenstaande voorbeeld illustreert de twee meest voorkomende gevallen van -exec:

  1. Als een test om de zoekopdracht verder te beperken.
  2. Om een of andere actie uit te voeren op de gevonden padnaam (meestal, maar niet noodzakelijk, aan het einde van het find commando).

Met -exec in combinatie met sh -c

Het commando dat -exec kan uitvoeren is beperkt tot een extern hulpprogramma met optionele argumenten.Het is niet mogelijk om ingebouwde shell, functies, conditionals, pipelines, redirections etc. direct met -exec te gebruiken, tenzij verpakt in iets als een sh -c child shell.

Als bash features vereist zijn, gebruik dan bash -c in plaats van sh -c.

sh -c voert /bin/sh uit met een script op de opdrachtregel, gevolgd door optionele opdrachtregelargumenten voor dat script.

Een eenvoudig voorbeeld van het gebruik van sh -c op zichzelf, zonder find :

sh -c "echo "You gave me $1, thanks!"" sh "apples" 

Dit geeft twee argumenten door aan het child shell-script. Deze worden in $0 en $1 geplaatst zodat het script kan worden gebruikt.

  1. De tekenreeks sh. Dit zal beschikbaar zijn als $0 in het script, en als de interne shell een foutmelding geeft, zal hij deze voorafgaan met deze string.

  2. Het argument apples is beschikbaar als $1 in het script, en als er meer argumenten waren geweest, dan zouden deze beschikbaar zijn geweest als $2, $3 etc. Ze zouden ook beschikbaar zijn in de lijst "$@" (behalve $0 die geen deel zou uitmaken van "$@").

Dit is handig in combinatie met -exec omdat het ons in staat stelt willekeurig complexe scripts te maken die inwerken op de padnamen gevonden door find.

Voorbeeld: vind alle reguliere bestanden met een bepaald achtervoegsel voor de bestandsnaam en verander dat achtervoegsel van de bestandsnaam in een ander achtervoegsel, waarbij de achtervoegsels in variabelen worden bewaard:

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" {} ";" 

Binnenin de inte rnal-script, $1 zou de tekenreeks text zijn, $2 zou de tekenreeks txt en $3 zou de padnaam zijn die find voor ons heeft gevonden. De parameteruitbreiding ${3%.$1} zou de padnaam gebruiken en het achtervoegsel .text eruit verwijderen.

Of gebruik dirname / basename:

find . -type f -name "*.$from" -exec sh -c " mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"" sh "$from" "$to" {} ";" 

of, met toegevoegde variabelen in de intern script:

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" {} ";" 

Merk op dat in deze laatste variant de variabelen from en to in de child shell onderscheiden zich van de variabelen met dezelfde namen in het externe script.

Het bovenstaande is de juiste manier om een willekeurig complex script aan te roepen van -exec met find. Het gebruik van find in een lus als

for pathname in $( find ... ); do 

is foutgevoelig en onelegant (persoonlijke mening). Het splitst bestandsnamen op witruimten, roept bestandsnaam globbing op, en dwingt de shell ook om het volledige resultaat van find uit te breiden voordat zelfs de eerste iteratie van de lus wordt uitgevoerd.

Zie ook:


Met -exec ... {} +

De ; aan het einde kan worden vervangen door +. Dit zorgt ervoor dat find het gegeven commando uitvoert met zoveel mogelijk argumenten (gevonden padnamen) in plaats van één keer voor elke gevonden padnaam. De string {} moet net voor de + voorkomen om dit te laten werken .

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} + 

Hier zal find de resulterende padnamen verzamelen en cat op zoveel mogelijk van hen tegelijk.

find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} + 

Evenzo zal mv worden uitgevoerd als zo min mogelijk. Dit laatste voorbeeld vereist GNU mv van coreutils (die de -t optie ondersteunt).

Met -exec sh -c ... {} + is ook een efficiënte manier om een reeks padnamen te doorlopen met een willekeurig complex script.

De basis is hetzelfde als bij het gebruik van -exec sh -c ... {} ";", maar het script heeft nu een veel langere lijst met argumenten nodig. Deze kunnen worden doorgelust door "$@" binnen het script te herhalen.

Ons voorbeeld uit de laatste sectie dat de achtervoegsels van bestandsnamen verandert:

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" {} + 

Met -execdir

Er is ook -execdir ( geïmplementeerd door de meeste find varianten, maar geen standaardoptie).

Dit werkt als -exec met het verschil dat het gegeven shell-commando wordt uitgevoerd met de directory van de gevonden padnaam als de huidige werkdirectory en dat {} zal de basisnaam van de gevonden padnaam bevatten zonder zijn pad (maar GNU find zal nog steeds de basisnaam voorvoegen met ./, terwijl BSD find dat niet zal doen).

Voorbeeld:

find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \; 

Hierdoor wordt elk gevonden *.txt -bestand verplaatst naar een reeds bestaande done-texts submap in dezelfde map als waar het bestand zich bevond gevonden . Het bestand wordt ook hernoemd door het achtervoegsel .done eraan toe te voegen.

Dit zou een beetje lastiger zijn om te doen met -exec aangezien we de basisnaam van het gevonden bestand uit {} zouden moeten halen om de nieuwe naam van het bestand te vormen. We hebben ook de directorynaam van {} nodig om de done-texts directory correct te lokaliseren.

Met -execdir, sommige dingen zoals deze worden gemakkelijker.

De overeenkomstige bewerking met -exec in plaats van -execdir zou een child shell moeten gebruiken:

find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done" done" sh {} + 

of,

find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "${name%/*}/done-texts/${name##*/}.done" done" sh {} + 

Opmerkingen

  • -exec neemt een programma en argumenten en voert het uit; sommige shell-commandos bestaan alleen uit een programma en argumenten, maar veel niet. Een shell-commando kan redirection en piping bevatten; -exec kan niet (hoewel de hele find kan worden omgeleid). Een shell-commando kan ; && if enz. Gebruiken; -exec kan niet, hoewel -a -o wel wat kan. Een shell-commando kan een alias of shell-functie zijn, of ingebouwd; -exec kan niet. Een shell-commando kan vars uitbreiden; -exec kan dat niet (hoewel de buitenschil waarop de find draait dat wel kan). Een shell-commando kan $(command) elke keer anders vervangen; -exec kan niet. …
  • Zeggen dat ' een shell-commando hier verkeerd is, find -exec cmd arg \; niet ' t roept een shell op om een shell-opdrachtregel te interpreteren, deze draait execlp("cmd", "arg") rechtstreeks, niet execlp("sh", "-c", "cmd arg") (waarvoor de shell zou uiteindelijk het equivalent doen van execlp("cmd", "arg") als cmd niet was ingebouwd).
  • Je zou kunnen verduidelijken dat alle de find argumenten na -exec en tot ; of + verzin het commando om samen met zijn argumenten uit te voeren, waarbij elke instantie van een {} argument vervangen is door het huidige bestand (met ;), en {} als het laatste argument voor + vervangen door een lijst met bestanden als afzonderlijke argumenten (in de {} + case). IOW -exec accepteert verschillende argumenten, beëindigd door een ; of {} +.
  • @Kusalananda Zou niet ' werken in je laatste voorbeeld ook met dit eenvoudiger commando: find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'?
  • @Atralb Ja, dat zou ook hebben gewerkt en hetzelfde effect hebben gehad als het laatste stuk code, maar in plaats van in een lus, eenmaal per gevonden bestand, voer je zowel sh als mv uit voor elk gevonden bestand, wat merkbaar langzamer voor grote hoeveelheden bestanden.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *