Jeg finder mig selv konstant i at kigge på syntaksen for
find . -name "FILENAME" -exec rm {} \;
primært fordi jeg ikke kan se, hvordan nøjagtigt -exec
-delen fungerer. Hvad er betydningen af seler, tilbageslag og semikolon? Er der andre brugssager til denne syntaks?
Kommentarer
Svar
Dette svar kommer i følgende dele:
- Grundlæggende brug af
-exec
- Brug af
-exec
i kombination medsh -c
- Brug af
-exec ... {} +
- Brug af
-execdir
Grundlæggende brug af -exec
Indstillingen -exec
tager et eksternt værktøj med valgfri argumenter som dets argument og udfører det.
Hvis strengen {}
er til stede hvor som helst i den givne kommando, erstattes hver forekomst af den stienavn, der aktuelt behandles ( fx ./some/path/FILENAME
). I de fleste skaller behøver de to tegn {}
ikke at blive citeret.
Kommandoen skal afsluttes med en ;
for find
for at vide, hvor den ender (da der kan være flere muligheder bagefter s). For at beskytte ;
fra skallen skal den citeres som \;
eller ";"
ellers ser skallen det som slutningen af find
kommandoen.
Eksempel (\
i slutningen af de to første linjer er kun for linjefortsætninger):
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";"
Dette finder alle almindelige filer (-type f
) hvis navne matcher mønsteret *.txt
i eller under den aktuelle bibliotek. Derefter tester det, om strengen hello
forekommer i nogen af de fundne filer ved hjælp af grep -q
(som ikke producerer nogen output, bare en exit status). For de filer, der indeholder strengen, udføres cat
for at sende indholdet af filen til terminalen.
Hver -exec
fungerer også som en “test” på stienavne fundet af find
, ligesom -type
og -name
gør. Hvis kommandoen returnerer nul udgangsstatus (betyder “succes”), betragtes den næste del af find
kommandoen, ellers betragtes find
kommandoen fortsætter med det næste stinavn. Dette bruges i eksemplet ovenfor for at finde filer, der indeholder strengen hello
, men for at ignorere alle andre filer.
Ovenstående eksempel illustrerer de to mest almindelige anvendelser tilfælde af -exec
:
- Som en test for yderligere at begrænse søgningen.
- At udføre en slags handling på det fundne stienavn (normalt, men ikke nødvendigvis, i slutningen af
find
kommandoen).
Brug af -exec
i kombination med sh -c
Den kommando, som -exec
kan udføre, er begrænset til et eksternt værktøj med valgfri argumenter.At bruge shell-indbyggede, funktioner, betingelser, rørledninger, omdirigeringer osv. Direkte med -exec
er ikke mulig, medmindre det er pakket ind i noget som en sh -c
child shell.
Hvis bash
-funktioner er påkrævet, skal du bruge bash -c
i stedet for sh -c
.
sh -c
kører /bin/sh
med et script givet på kommandolinjen, efterfulgt af valgfri kommandolinjeargumenter til dette script.
Et simpelt eksempel på at bruge sh -c
i sig selv uden find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Dette sender to argumenter til underordnet shell-script. Disse placeres i $0
og $1
for at scriptet skal bruges.
-
streng
sh
. Dette vil være tilgængeligt som$0
inde i scriptet, og hvis den interne shell udsender en fejlmeddelelse, vil den præfikse den med denne streng. -
Argumentet
apples
er tilgængeligt som$1
i scriptet, og hvis der var flere argumenter, ville disse have været tilgængelige som$2
,$3
osv. De ville også være tilgængelige på listen"$@"
$0
som ikke ville være en del af"$@"
).
Dette er nyttigt i kombination med -exec
, da det giver os mulighed for at lave vilkårligt komplekse scripts, der virker på de stienavne, der findes af find
.
Eksempel: Find alle almindelige filer, der har et bestemt filnavn-suffiks, og skift filnavn-suffikset til et andet suffiks, hvor suffikserne holdes 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" {} ";"
Inde i integrationen rnalt script, $1
ville være strengen text
, $2
ville være strengen txt
og $3
ville være det sti, som find
har fundet for os. Parameterudvidelsen ${3%.$1}
tager stienavnet og fjerner suffikset .text
fra det.
Eller ved hjælp af dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c " mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"" sh "$from" "$to" {} ";"
eller med tilføjede variabler i internt 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" {} ";"
Bemærk, at variablerne from
og i underskallen adskiller sig fra variablerne med de samme navne i det eksterne script.
Ovenstående er den korrekte måde at kalde et vilkårligt komplekst script på fra -exec
med find
. Brug af find
i en løkke som
for pathname in $( find ... ); do
er fejlbehæftet og uelegant (personlig mening). Det splitter filnavne på mellemrum, påkalder filnavn globbing og tvinger også skallen til at udvide det komplette resultat af find
, før den endda kører den første iteration af sløjfen.
Se også:
- Hvorfor er looping over find ' s output dårlig praksis?
- Er det muligt at bruge `find -exec sh -c` sikkert?
Brug af -exec ... {} +
;
i slutningen kan erstattes af +
. Dette får find
til at udføre den givne kommando med så mange argumenter (fundne stinavne) som muligt snarere end en gang for hvert fundne stinavn. Strengen {}
skal forekomme lige før +
for at dette skal fungere .
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Her vil find
samle de resulterende stienavne og udføre cat
på så mange af dem som muligt på én gang.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} +
Ligeledes her vil mv
blive udført som få gange som muligt. Dette sidste eksempel kræver GNU mv
fra coreutils (som understøtter -t
-indstillingen).
Brug af -exec sh -c ... {} +
er også en effektiv måde at løbe over et sæt stienavne med et vilkårligt komplekst script.
Det grundlæggende er det samme som når du bruger -exec sh -c ... {} ";"
, men scriptet tager nu en meget længere liste over argumenter. Disse kan overføres ved at løkke over "$@"
inde i scriptet.
Vores eksempel fra det sidste afsnit, der ændrer filnavnesuffikser:
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" {} +
Brug af -execdir
Der er også -execdir
( implementeret af de fleste find
varianter, men ikke en standardindstilling).
Dette fungerer som -exec
med den forskel, at den givne shell-kommando udføres med biblioteket for det fundne stinavn som dets aktuelle arbejdsmappe, og at {}
indeholder basenavnet på det fundne stinavn uden dets sti (men GNU find
vil stadig præfix basenavnet med ./
, mens BSD find
ikke gør det).
Eksempel:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Dette vil flytte hver fundet *.txt
-fil til en allerede eksisterende done-texts
underkatalog i samme bibliotek som hvor filen var fundet . Filen omdøbes også ved at tilføje suffikset .done
til det.
Dette ville være lidt vanskeligere at gøre med -exec
da vi bliver nødt til at få basisnavnet på den fundne fil ud af {}
for at danne det nye navn på filen. Vi har også brug for katalognavnet fra {}
for at finde done-texts
-mappen korrekt.
Med -execdir
, nogle ting som disse bliver lettere.
Den tilsvarende handling ved hjælp af -exec
i stedet for -execdir
skal bruge en underordnet shell:
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
tager et program og argumenter og kører det; nogle shell-kommandoer består kun af et program og argumenter, men mange gør det ikke. En shell-kommando kan omfatte omdirigering og piping;-exec
kan ikke (selvom helefind
kan omdirigeres). En shell-kommando kan bruge; && if
osv.-exec
kan ikke, selvom-a -o
kan gøre noget. En shell-kommando kan være et alias eller shell-funktion eller indbygget;-exec
kan ikke. En shell-kommando kan udvide vars;-exec
kan ikke (selvom den ydre skal, der kørerfind
, kan). En shell-kommando kan erstatte$(command)
forskelligt hver gang;-exec
kan ikke. … - At sige det ' en shell-kommando er forkert her,
find -exec cmd arg \;
betyder ikke ' t påberåber en shell for at fortolke en shell-kommandolinje, den kørerexeclp("cmd", "arg")
direkte, ikkeexeclp("sh", "-c", "cmd arg")
(for hvilken shell ville ende med at udføre det ækvivalente medexeclp("cmd", "arg")
hviscmd
ikke var indbygget). - Du kunne præcisere, at alle
find
argumenter efter-exec
og op til;
eller+
udgør kommandoen til at udføre sammen med sine argumenter med hver forekomst af et{}
argument erstattet med den aktuelle fil (med;
) og{}
som det sidste argument før+
erstattet med en liste over filer som separate argumenter (i{} +
sag). IOW-exec
tager flere argumenter, afsluttet med en;
eller{}
+
. - @Kusalananda Ville ' ikke dit sidste eksempel også arbejde med denne enklere kommando:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Ja, det ville også have fungeret og haft samme effekt som det sidste stykke kode, men i stedet for at køre
mv
i en løkke, en gang pr. fundet fil, udfører du bådesh
ogmv
for hver fundet fil, som vil være mærkbart langsommere for store mængder filer.
man
side læser Et værktøjsnavn eller argument der kun indeholder de to tegn " {} " skal erstattes d ved det nuværende stinavn , som synes at være tilstrækkelig for mig. Derudover har det et eksempel med-exec rm {} \;
, ligesom i dit spørgsmål. I mine dage var der næppe andre ressourcer end " store grå mur ", trykte bøgerman
sider (papir var skarpere end opbevaring). Så jeg ved, at dette er tilstrækkeligt for nogen, der er nye inden for emnet. Dit sidste spørgsmål er dog rimeligt at stille her. Uheldigvis har hverken @Kusalananda eller jeg selv noget svar på det.