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
Svar
Detta svar finns i följande delar:
- Grundläggande användning av
-exec
- Användning av
-exec
i kombination medsh -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
:
- Som ett test för att ytterligare begränsa sökningen.
- 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.
-
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. -
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å:
- Varför är looping över hitta ’ s utgång dålig praxis?
- Är det möjligt att använda `hitta -exec sh -c` säkert?
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 helafind
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örfind
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örexeclp("cmd", "arg")
direkt, inteexeclp("sh", "-c", "cmd arg")
(för vilken shell skulle sluta göra motsvarigheten tillexeclp("cmd", "arg")
omcmd
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ådesh
ochmv
för varje fil som hittas märkbart långsammare för stora mängder filer.
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öckerman
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.