Jag befinner mig ständigt på att leta upp syntaxen för

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

främst för att jag inte ser hur exakt -exec -delen fungerar. Vad är innebörden av hängslen, backslash och semikolon? Finns det andra användningsfall för den syntaxen?

Kommentarer

  • @Philippos: Jag förstår er poäng. Kom ihåg att man-sidorna är en referens, dvs användbar för dem med en förståelse för saken för att slå upp syntaxen. För någon som är ny inom ämnet är de ofta kryptiska och formella för att vara användbara. Du kommer att upptäcka att det accepterade svaret är ungefär tio gånger så långt som man-sida-posten, och att ’ är av en anledning.
  • Även den gamla POSIX man sidan läser Ett verktygsnamn eller argument som endast innehåller de två tecknen ” {} ” ska ersättas d av det aktuella sökvägen , vilket verkar vara tillräckligt för mig. Dessutom har det ett exempel med -exec rm {} \;, precis som i din fråga. På mina dagar fanns det knappast några andra resurser än ” stora grå vägg ”, tryckta böcker man sidor (papper var billigare än lagring). Så jag vet att detta är tillräckligt för någon ny inom ämnet. Din sista fråga är dock rättvis att ställa här. Tyvärr har varken @Kusalananda eller jag själv svar på det.
  • @Philippos 🙂 Åh, det skulle vara en spännande sak att försöka, för en nivå av ” spännande ” som ligger utanför skalan för mitt diagram.
  • Comeon @Philippos. Berättar du verkligen för Kusalananda att han inte förbättrade arbetssidan? 🙂
  • @ZsoltSzilagy Jag har varken sagt det eller menat det. Han matade dig väldigt bra, jag tror bara att du är tillräckligt gammal för att äta själv. (-;

Svar

Detta svar finns i följande delar:

  • Grundläggande användning av -exec
  • Användning av -exec i kombination med sh -c
  • Med -exec ... {} +
  • Användning av -execdir

Grundläggande användning av -exec

Alternativet -exec tar ett externt verktyg med valfria argument som argumentet och kör det.

Om strängen {} finns någonstans i det givna kommandot kommer varje förekomst av den att ersättas med sökvägen som för närvarande behandlas ( t.ex. ./some/path/FILENAME). I de flesta skal behöver de två tecknen {} inte citeras.

Kommandot måste avslutas med en ; för find för att veta var det slutar (eftersom det kan finnas fler alternativ efteråt s). För att skydda ; från skalet måste det citeras som \; eller ";" annars kommer skalet att se det som slutet på find -kommandot.

Exempel (\ slutet på de två första raderna är bara för linjefortsättningar):

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

Detta hittar alla vanliga filer (-type f) vars namn matchar mönstret *.txt i eller under den aktuella katalogen. Det kommer sedan att testa om strängen hello förekommer i någon av de hittade filerna med grep -q (som inte ger någon utdata, bara en utgång status). För de filer som innehåller strängen kommer cat att köras för att mata ut innehållet i filen till terminalen.

Varje -exec fungerar också som ett ”test” på sökvägarna som hittats av find, precis som -type och -name gör det. Om kommandot returnerar en nollutgångsstatus (vilket betyder ”framgång”) beaktas nästa del av find -kommandot, annars tas find kommandot fortsätter med nästa sökväg. Detta används i exemplet ovan för att hitta filer som innehåller strängen hello, men för att ignorera alla andra filer.

Ovanstående exempel illustrerar de två vanligaste användningarna fall av -exec:

  1. Som ett test för att ytterligare begränsa sökningen.
  2. Att utföra någon form av åtgärd på den hittade sökväg (vanligtvis men inte nödvändigtvis i slutet av find -kommandot).

Använd -exec i kombination med sh -c

Kommandot som -exec kan utföra är begränsat till ett externt verktyg med valfria argument.Att använda inbyggda skal, funktioner, villkor, rörledningar, omdirigeringar etc. direkt med -exec är inte möjligt, om inte inslagna i något som en sh -c underskal.

Om bash -funktioner krävs, använd sedan bash -c istället för sh -c.

sh -c kör /bin/sh med ett skript på kommandoraden, följt av valfria kommandoradsargument till det skriptet.

Ett enkelt exempel på att använda sh -c i sig själv utan find :

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

Detta skickar två argument till det underordnade skalskriptet. Dessa kommer att placeras i $0 och $1 för att manuset ska kunna användas.

  1. The sträng sh. Detta kommer att finnas tillgängligt som $0 inuti skriptet, och om det interna skalet matar ut ett felmeddelande kommer det att förpeka det med den här strängen.

  2. Argumentet apples är tillgängligt som $1 i skriptet, och om det hade funnits fler argument hade dessa varit tillgängliga som $2, $3 etc. De skulle också vara tillgängliga i listan "$@" $0 som inte skulle vara en del av "$@").

Detta är användbart i kombination med -exec eftersom det tillåter oss att skapa godtyckligt komplexa skript som verkar på sökvägarna som hittas av find.

Exempel: Hitta alla vanliga filer som har ett visst filnamnssuffix och ändra det filnamnssuffixet till något annat suffix, där suffixen förvaras i variabler:

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

Inuti integrationen rnalt skript, $1 skulle vara strängen text, $2 skulle vara strängen txt och $3 skulle vara vad sökvägen find har hittat för oss. Parameterutvidgningen ${3%.$1} tar sökvägen och tar bort suffixet .text från den.

Eller med dirname / basename:

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

eller med tillagda variabler i internt skript:

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

Observera att variablerna from och i underskalet skiljer sig från variablerna med samma namn i det externa skriptet.

Ovanstående är det rätta sättet att anropa ett godtyckligt komplext skript från -exec med find. Att använda find i en slinga som

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

är felbenägen och inelegant (personlig åsikt). Det delar upp filnamn på mellanslag, åberopar filnamn globbing och tvingar också skalet att expandera det fullständiga resultatet av find innan du till och med kör den första iterationen av slingan.

Se också:


Med -exec ... {} +

; i slutet kan ersättas med +. Detta gör att find kör det givna kommandot med så många argument (hittade sökvägar) som möjligt snarare än en gång för varje hittat sökväg. Strängen {} måste förekomma precis innan + för att detta ska fungera .

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

Här samlar find de resulterande sökvägarna och kör cat på så många av dem som möjligt samtidigt.

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

På samma sätt kommer mv att utföras som några gånger som möjligt. Det sista exemplet kräver GNU mv från coreutils (som stöder alternativet -t).

Använd -exec sh -c ... {} + är också ett effektivt sätt att slingra över en uppsättning söknamn med ett godtyckligt komplext skript.

Grunderna är desamma som när du använder -exec sh -c ... {} ";", men skriptet tar nu en mycket längre lista med argument. Dessa kan slingras genom att slingra över "$@" inuti skriptet.

Vårt exempel från det sista avsnittet som ändrar filnamnssuffix: ”b398bedbfe”>


Med -execdir

Det finns också -execdir ( implementeras av de flesta find varianter, men inte ett standardalternativ).

Detta fungerar som -exec med skillnaden att det givna skalkommandot körs med katalogen för det hittade sökvägen som dess aktuella arbetskatalog och att {} kommer att innehålla basnamnet för det sökväg som hittats utan dess sökväg (men GNU find kommer fortfarande att prefixa basnamnet med ./, medan BSD find inte gör det).

Exempel:

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

Detta flyttar alla hittade *.txt -filer till en befintlig done-texts underkatalog i samma katalog som där filen var hittades . Filen kommer också att döpas om genom att lägga till suffixet .done till det.

Det skulle vara lite svårare att göra med -exec eftersom vi måste få basnamnet på den hittade filen ur {} för att bilda filens nya namn. Vi behöver också katalognamnet från {} för att hitta katalogen done-texts korrekt.

Med -execdir, vissa saker som dessa blir lättare.

Motsvarande operation med -exec istället för -execdir måste anställa ett underskal:

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

eller,

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

Kommentarer

  • -exec tar ett program och argument och kör det; vissa skalkommandon består endast av ett program och argument men många gör det inte. Ett skalkommando kan inkludera omdirigering och piping; -exec kan inte (även om hela find kan omdirigeras). Ett skalkommando kan använda ; && if etc; -exec kan inte, även om -a -o kan göra något. Ett skalkommando kan vara ett alias eller skalfunktion eller inbyggt; -exec kan inte. Ett skalkommando kan expandera vars; -exec kan inte (även om det yttre skalet som kör find kan). Ett skalkommando kan ersätta $(command) på olika sätt varje gång; -exec kan inte. …
  • Att säga det ’ ett skalkommando är fel här, find -exec cmd arg \; betyder inte ’ t åberopar ett skal för att tolka en skalkommandorad, den kör execlp("cmd", "arg") direkt, inte execlp("sh", "-c", "cmd arg") (för vilken shell skulle sluta göra motsvarigheten till execlp("cmd", "arg") om cmd inte var inbyggd).
  • Du kan klargöra att allt find argument efter -exec och upp till ; eller + utgör kommandot att köra tillsammans med dess argument, med varje instans av ett {} -argument ersatt med den aktuella filen (med ;) och {} som det sista argumentet före + ersatt med en lista med filer som separata argument (i {} + fall). IOW -exec tar flera argument, avslutas med ett ; eller {} +.
  • @Kusalananda Skulle ’ inte ditt sista exempel också fungera med det här enklare kommandot: find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'?
  • @Atralb Ja, det skulle också ha fungerat och haft samma effekt som den sista koden, men istället för att köra mv i en slinga, en gång per hittad fil, kör du både sh och mv för varje fil som hittas märkbart långsammare för stora mängder filer.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *