Jeg finner meg selv hele tiden og ser på syntaksen til
find . -name "FILENAME" -exec rm {} \;
hovedsakelig fordi jeg ikke ser hvordan nøyaktig delen -exec
fungerer. Hva er betydningen av seler, tilbakeslag og semikolon? Er det andre brukstilfeller for den syntaksen?
Kommentarer
Svar
Dette svaret kommer i følgende deler:
- Grunnleggende bruk av
-exec
- Bruk av
-exec
i kombinasjon medsh -c
- Bruke
-exec ... {} +
- Bruke
-execdir
Grunnleggende bruk av -exec
Alternativet -exec
tar et eksternt verktøy med valgfrie argumenter som argumentet og utfører det.
Hvis strengen {}
er til stede hvor som helst i den gitte kommandoen, vil hver forekomst av den bli erstattet av banenavnet som for tiden behandles ( f.eks. ./some/path/FILENAME
). I de fleste skjell trenger ikke de to tegnene {}
å siteres.
Kommandoen må avsluttes med en ;
for find
for å vite hvor den ender (da det kan være flere alternativer etterpå s). For å beskytte ;
fra skallet, må det siteres som \;
eller ";"
ellers vil skallet se det som slutten på find
-kommandoen.
Eksempel (\
på slutten av de to første linjene er bare for linjefortsettelser):
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";"
Dette finner alle vanlige filer (-type f
) hvis navn samsvarer med mønsteret *.txt
i eller under den nåværende katalogen. Deretter vil den teste om strengen hello
forekommer i noen av de funnet filene ved hjelp av grep -q
(som ikke gir noen utgang, bare en utgang status). For de filene som inneholder strengen, vil cat
kjøres for å sende innholdet av filen til terminalen.
Hver -exec
fungerer også som en «test» på banenavnene som er funnet av find
, akkurat som -type
og -name
gjør. Hvis kommandoen returnerer en nullutgangsstatus (som betyr «suksess»), blir neste del av find
-kommandoen vurdert, ellers blir find
kommandoen fortsetter med neste banenavn. Dette brukes i eksemplet ovenfor for å finne filer som inneholder strengen hello
, men for å ignorere alle andre filer.
Eksemplet ovenfor illustrerer de to vanligste bruken tilfeller av -exec
:
- Som en test for å begrense søket ytterligere.
- For å utføre en slags handling på funnet stienavn (vanligvis, men ikke nødvendigvis, på slutten av
find
-kommandoen).
Bruk av -exec
i kombinasjon med sh -c
Kommandoen som -exec
kan utføre er begrenset til et eksternt verktøy med valgfrie argumenter.Å bruke shell-innebygde funksjoner, betingelser, rørledninger, omdirigeringer osv. Direkte med -exec
er ikke mulig, med mindre det er pakket inn i noe som en sh -c
child shell.
Hvis bash
funksjoner kreves, bruk bash -c
i stedet for sh -c
.
sh -c
kjører /bin/sh
med et skript gitt på kommandolinjen, etterfulgt av valgfrie kommandolinjeargumenter til skriptet.
Et enkelt eksempel på å bruke sh -c
i seg selv, uten find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Dette sender to argumenter til underordnet skallskript. Disse blir plassert i $0
og $1
for at skriptet skal brukes.
-
The streng
sh
. Dette vil være tilgjengelig som$0
inne i skriptet, og hvis det interne skallet sender ut en feilmelding, vil det prefikse den med denne strengen. -
Argumentet
apples
er tilgjengelig som$1
i skriptet, og hadde det vært flere argumenter, ville disse vært tilgjengelige som$2
,$3
osv. De ville også være tilgjengelige i listen"$@"
$0
som ikke ville være en del av"$@"
).
Dette er nyttig i kombinasjon med -exec
da det tillater oss å lage vilkårlig komplekse skript som virker på banenavnene som er funnet av find
.
Eksempel: Finn alle vanlige filer som har et bestemt suffiks for filnavn, og endre dette filnavnet til et annet suffiks, der suffiksen 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" {} ";"
Inne i integrasjonen rnalt skript, $1
ville være strengen text
, $2
ville være strengen txt
og $3
ville være det stienavnet find
har funnet for oss. Parameterutvidelsen ${3%.$1}
tar stienavnet og fjerner suffikset .text
fra den.
Eller bruker dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c " mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"" sh "$from" "$to" {} ";"
eller med tilleggsvariabler 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" {} ";"
Merk at i denne siste variasjonen er variablene from
og to
i underskallet skiller seg fra variablene med samme navn i det eksterne skriptet.
Ovenfor er den riktige måten å kalle et vilkårlig komplekst skript fra -exec
med find
. Å bruke find
i en sløyfe som
for pathname in $( find ... ); do
er feil utsatt og uelegant (personlig mening). Det splitter filnavn på mellomrom, påkaller filnavn globbing, og tvinger også skallet til å utvide hele resultatet av find
før du til og med kjører den første iterasjonen av sløyfen.
Se også:
- Hvorfor er sløyfing over finne ' s utgang dårlig praksis?
- Er det mulig å bruke `finn -exec sh -c` trygt?
Bruk av -exec ... {} +
;
på slutten kan erstattes av +
. Dette fører til at find
utfører den gitte kommandoen med så mange argumenter (funnet stienavn) som mulig i stedet for en gang for hvert funnet stinavn. Strengen {}
må forekomme like før +
for at dette skal fungere .
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Her vil find
samle de resulterende banenavnene og utføre cat
på så mange av dem som mulig samtidig.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} +
På samme måte vil mv
bli utført som få ganger som mulig. Dette siste eksemplet krever GNU mv
fra coreutils (som støtter -t
-alternativet).
Bruk av -exec sh -c ... {} +
er også en effektiv måte å løpe over et sett med banenavn med et vilkårlig komplekst skript.
Grunnleggende er det samme som når du bruker -exec sh -c ... {} ";"
, men skriptet tar nå en mye lengre liste over argumenter. Disse kan sløyfes ved å løkke over "$@"
inne i skriptet.
Vårt eksempel fra den siste delen som endrer filnavnssuffikser:
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" {} +
Bruk av -execdir
Det er også -execdir
( implementert av de fleste find
varianter, men ikke et standardalternativ).
Dette fungerer som -exec
med den forskjellen at den gitte shell-kommandoen kjøres med katalogen til det funnet banenavnet som den nåværende arbeidskatalogen og at {}
inneholder basenavnet til det funnet stienavnet uten banen (men GNU find
vil fremdeles prefikse basenavnet med ./
, mens BSD find
ikke vil gjøre det).
Eksempel:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Dette vil flytte hvert funnet *.txt
-fil til en eksisterende done-texts
underkatalog i samme katalog som der filen var funnet . Filen vil også bli omdøpt ved å legge til suffikset .done
til den.
Dette ville være litt vanskeligere å gjøre med -exec
da vi måtte få grunnnavnet til den funnet filen ut av {}
for å danne det nye navnet på filen. Vi trenger også katalognavnet fra {}
for å finne done-texts
katalogen riktig.
Med -execdir
, noen ting som disse blir enklere.
Den tilsvarende operasjonen bruker -exec
i stedet for -execdir
måtte ansette et barneskall:
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 et program og argumenter og kjører det; noen skallkommandoer består bare av et program og argumenter, men mange gjør det ikke. En skallkommando kan omfatte omdirigering og piping;-exec
kan ikke (selv om helefind
kan omdirigeres). En skallkommando kan bruke; && if
etc;-exec
kan ikke, selv om-a -o
kan gjøre noe. En skallkommando kan være et alias eller skallfunksjon, eller innebygd;-exec
kan ikke. En skallkommando kan utvide vars;-exec
kan ikke (selv om det ytre skallet som kjørerfind
, kan). En skallkommando kan erstatte$(command)
forskjellig hver gang;-exec
kan ikke. … - Å si det ' en shell-kommando er feil her,
find -exec cmd arg \;
betyr ikke ' t påkaller et skall for å tolke en skallkommandolinje, den kjørerexeclp("cmd", "arg")
direkte, ikkeexeclp("sh", "-c", "cmd arg")
(som shell vil ende opp med å gjøre det som tilsvarerexeclp("cmd", "arg")
hviscmd
ikke var innebygd). - Du kan avklare at alle
find
argumentene etter-exec
og opp til;
eller+
utgjør kommandoen for å utføre sammen med argumentene, med hver forekomst av et{}
argument erstattet med den nåværende filen (med;
), og{}
som siste argument før+
erstattet med en liste over filer som separate argumenter (i{} +
sak). IOW-exec
tar flere argumenter, avsluttet med en;
eller{}
+
. - @Kusalananda Ville ikke ' t ditt siste eksempel også fungerer med denne enklere kommandoen:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Ja, det ville også ha fungert og hatt samme effekt som den siste koden, men i stedet for å kjøre
mv
i en løkke, én gang per funnet fil, utfører du bådesh
ogmv
for hver funnet fil, som vil være merkbart langsommere for store mengder filer.
man
-siden leser Et verktøynavn eller argument inneholder bare de to tegnene " {} " skal erstattes d av det nåværende stienavnet , som synes å være tilstrekkelig for meg. I tillegg har den et eksempel med-exec rm {} \;
, akkurat som i spørsmålet ditt. I mine dager var det knapt andre ressurser enn " stor grå vegg ", bøker med trykteman
sider (papir var billigere enn lagring). Så jeg vet at dette er tilstrekkelig for noen som er nye i emnet. Det siste spørsmålet ditt er imidlertid greit å stille her. Dessverre har verken @Kusalananda eller meg selv svar på det.