find i dybden
Kapittelet Filnavigering
presenterte find i en tabell: et mønster her, en alder der, én
-exec-linje i bunnen. Det strekker til å kopiere en kommando som virker.
Denne guiden er steget forbi kopieringen, for find er ett av få verktøy der
rekkefølgen på ordene endrer hva kommandoen gjør, i ett tilfelle nok til å
tømme en mappe ved et uhell.
Alt her er sjekket på Ubuntu 26.04 og Fedora 44 (findutils 4.10.0) og Arch (4.11.0), med samme oppførsel på alle tre. Kommandoen følger med basissystemet i hver av disse distribusjonsfamiliene, så det er ingenting å installere før det første eksempelet.
Én filterkjede, venstre til høyre
find tar en startmappe og, om du vil, en kjede av tester og handlinger.
Uten noen av delene lister den startpunktet og alt under det, rekursivt.
Bygg denne lille sandkassen å søke i:
mkdir -p ~/logs/cache ~/logs/old ~/logs/sizes
cd ~/logs
touch app.log cache.log debug.log junk1.tmp junk2.tmp
touch "space test.log"
touch cache/session.log
echo "rotate failed on Tuesday" > notes.md
printf 'x' > sizes/one.txt # exactly one byte
touch sizes/empty.txt # zero bytes
head -c 1572864 /dev/zero > sizes/big.bin # 1.5 MiB of zeros
touch -d "1 day ago" mark.txt # -d sets the file's timestamp
touch -d "9 days ago" old/keep.txt # aged like the logs, but not a .log
ln -s app.log latest.log # a symlink (see chapter 3)
Alle eksemplene nedenfor kjører mot denne mappen, og hvert ord etter
startstien er en test eller en handling. Tester lenkes med en implisitt OG:
find ~/logs -name "*.tmp" -type f krever at både navn og type matcher.
Rekkefølgen betyr mer enn den ser ut som, for find evaluerer kjeden venstre til høyre. Se hva en test som kommer etter en handling ikke får til:
$ find ~/logs -name "*.tmp" -print -name junk1.tmp
/home/user/logs/junk1.tmp
/home/user/logs/junk2.tmp
$ find ~/logs -name junk1.tmp -print
/home/user/logs/junk1.tmp
Den andre -name-en står etter -print, så utskriften har allerede skjedd
når find kommer dit. En test skrevet etter en handling påvirker aldri den
handlingen. Det høres ut som en kuriositet helt til du møter -delete, som
også er en handling, og da er det den viktigste setningen i denne guiden.
Én trøst på veien: -print er standardhandlingen, så å utelate den endrer
ingenting.
På navn og type
-name skiller mellom store og små bokstaver, og -iname ignorerer dem:
find ~/logs -iname "*.MD" finner notes.md, der -name "*.MD" kommer
tom tilbake.
Nå den vanligste find-feilen av alle, satt opp et sted der globben faktisk
matcher. I en mappe med flere .txt-filer, for eksempel Dokumenter-mappen
din, prøv mønsteret uten anførselstegn:
$ find . -name *.txt
find: paths must precede expression: `aa.txt'
find: possible unquoted pattern after predicate `-name'?
Exit status 1, ingen treff. Skallet utvidet mønsteret til de matchende
filnavnene før find i det hele tatt startet (kapittelet
Shell-grunnleggende
dekker globber) og serverte det ekstra argumenter det aldri hadde bedt om.
Det irriterende: i en mappe uten .txt-filer i det hele tatt virker den
samme kommandoen, for en glob uten treff når find urørt som et bokstavelig
mønster. Om det feiler, avhenger av hva som tilfeldigvis ligger der. Sett
anførselstegn rundt mønsteret, find . -name "*.txt", og problemet
forsvinner.
-type filtrerer på hva en ting er: -type f for filer, -type d for
mapper, -type l for symlenker. Sandkassen har én symlenke, og den viser
hvor strengt find trekker disse grensene som standard:
$ find ~/logs -name latest.log -type f
$ find ~/logs -name latest.log -type l
/home/user/logs/latest.log
$ find -L ~/logs -name latest.log -type f
/home/user/logs/latest.log
En symlenke til en fil er ikke en fil så vidt find angår, så -type f
hopper over den og -type l tar selve lenken. (ln -s, som laget den,
hører hjemme i kapittelet
Filer og mapper.)
-L plassert før startstien (det er et alternativ, ikke en test) får find
til å følge lenker, og da matcher målet -type f. Etter stien blir det en
unknown-predicate-feil i stedet.
På alder og størrelse
Få fire av loggene til å se gamle ut med
touch -d "9 days ago" cache.log debug.log old/app.log old/error.log,
og søk så:
$ find ~/logs -name "*.log" -type f -mtime +7
/home/user/logs/cache.log
/home/user/logs/debug.log
/home/user/logs/old/app.log
/home/user/logs/old/error.log
-mtime måler alder i hele dager og runder opp. -mtime 0 fanger de
siste 24 timene, -mtime 7 fanger dag syv (en fil 7 til 8 dager gammel),
og -mtime +7 fanger alt eldre enn 7 hele dager, som er det som nettopp
matchet. Samme +/--mønster virker i minutter også: -mmin -30 treffer
filer endret for mindre enn 30 minutter siden, -mmin +30 filer eldre
enn det.
-newer FILE finner ting endret nyere enn FILE. Sandkassen har
mark.txt fra i går, så loggene yngre enn i går er:
$ find ~/logs -name "*.log" -newer ~/logs/mark.txt -type f
/home/user/logs/app.log
/home/user/logs/cache/session.log
/home/user/logs/space test.log
Kombiner den med -type f som regel: startmappen har en mtime også, og
når filer opprettes i den, kan mappen dukke opp i -newer-treffene ved
siden av filene. -type f-testen holder listen til filer.
Størrelse tar faste enheter, og alle runder opp:
| Det du skriver | Slik tolker find det |
|---|---|
n (bart tall) |
512-byte-blokker |
c |
byte |
k |
kibibytes (1024 byte) |
M |
mebibytes (1024 KiB) |
G |
gibibytes (1024 MiB) |
Avrundingen gir resultater som ser feil ut helt til du ser regelen:
$ find ~/logs/sizes -type f -size 1M
/home/user/logs/sizes/one.txt
$ find ~/logs/sizes -type f -size +1M
/home/user/logs/sizes/big.bin
$ find ~/logs/sizes -type f -size 2M
/home/user/logs/sizes/big.bin
one.txt er én byte lang, og find runder den opp til en hel mebibyte,
rausere blir det ikke. Alt fra 1 byte til nøyaktig 1 MiB matcher
-size 1M, mens +1M betyr strengt større. big.bin på 1.5 MiB rundes
opp igjen, og det er derfor den svarer på -size 2M. empty.txt på null
byte matcher ingen av delene, siden null runder til null. For tomme ting
spesielt finnes -empty, som matcher tomme filer og, like nyttig, tomme
mapper.
På eier og rettigheter
-user NAME og -group NAME filtrerer på eierskap. På en
skrivebordsmaskin med én bruker bekrefter de som regel det du forventet,
men på en maskin med flere kontoer deler de resultatene pent opp:
find /home -user emil gir deg Emils filer og ingen andres.
Rettigheter har tre skrivemåter, og forskjellen mellom dem er hele
spillet. -perm 644 matcher bare filer som er nøyaktig rw-r--r--.
-perm -644 matcher filer med alle de bitene satt, så en 755-fil
kvalifiserer også. -perm /700 matcher alt der eieren har minst én av de
tre bitene.
Den klassiske bruken er å granske etter filer som er skrivbare for alle,
med -0002: alle biter i 0002 må være satt, og 0002 er skrivebiten
for alle andre. Sett ut én og se:
$ chmod 666 notes.md
$ find ~/logs -type f -perm -0002
/home/user/logs/notes.md
$ chmod 644 notes.md
Alt dette søket finner, kan skrives av hver eneste konto på maskinen, og
det er sjelden meningen. Kjører du granskingen uten -type f, legger du
til ! -type l for å holde symlenker ut av treffene: rettighetene deres
er alltid helt åpne, så hver lenke ville matchet testen.
Én advarsel mens vi først er her. Eldre skript har av og til -perm +644
med seg. Den formen ble fjernet fra GNU findutils for flere år siden og
stopper nå find med en invalid-mode-feil i stedet for å gjøre noe nyttig.
Å styre søket
Tre alternativer styrer hvor find leter i stedet for hva den matcher.
-maxdepth N stopper nedstigningen etter N nivåer, og paret
-mindepth 1 -maxdepth 1 lister en mappes egne oppføringer, uten å gå
ned og uten å liste selve mappen:
$ find ~/logs -mindepth 1 -maxdepth 1 -type d
/home/user/logs/cache
/home/user/logs/old
/home/user/logs/sizes
-prune er sterkere: den forteller find at den ikke skal gå inn i en
matchet mappe i det hele tatt. Den opptrer nesten alltid i dette paret,
der venstre side matcher det som skal hoppes over og høyre side skriver
ut resten:
$ mkdir -p /tmp/prunedemo/node_modules
$ touch /tmp/prunedemo/node_modules/dep.js
$ find /tmp/prunedemo
/tmp/prunedemo
/tmp/prunedemo/node_modules
/tmp/prunedemo/node_modules/dep.js
$ find /tmp/prunedemo -name node_modules -prune -o -print
/tmp/prunedemo
node_modules og alt under den er borte, og selve mappen som ble hoppet
over, skrives heller ikke ut. Alle som har ventet på at en backup skulle
krype gjennom node_modules, kjenner igjen bruksområdet.
Én kombinasjon å nekte blankt: prune og -delete lar seg ikke kombinere.
Ber du find om begge, nekter den jobben før den i det hele tatt starter,
skriver en advarsel på stderr, avslutter med exit status 1, og sletter
ingenting, ikke engang filer en forhåndsvisning hadde listet:
$ find /tmp/prunedemo -name node_modules -prune -o -type f -mtime +7 -delete
find: The -delete action automatically turns on -depth, but -prune does nothing when -depth is in effect. If you want to carry on anyway, just explicitly use the -depth option.
Fellen er advarselens eget råd. Legger du til en eksplisitt -depth,
forsvinner nektingen: prune blir en no-op, find sletter i stillhet alle
matchende filer, også de inne i node_modules, og avslutter med
exit status 0 uten å mukke. (Ferske filer og ikke-tomme mapper overlever.)
Én grunn til at forhåndsvisningsvanen betyr noe: den utskrevne listen er
det som forteller deg at en sletting er trygg.
-xdev forteller find at den ikke skal gå ned i mapper som ligger på et
annet filsystem. Startpunktet og dets eget filsystem søkes alltid. Det
betyr noe for vide søk, for /proc og venner ligger på filsystemer helt
for seg selv (guiden
Filsystemet
tar deg med på omvisning i dem). Et søk gjennom hele treet gjør
forskjellen synlig. Treffet utenfor /proc ligger der distribusjonen din
legger det, og kommandoen skriver også ut Permission denied-linjer på
stderr, mer om dem om litt:
$ find / -name uptime
/proc/uptime
/usr/bin/uptime
$ find / -xdev -name uptime
/usr/bin/uptime
Med -xdev forlater søket aldri rotfilsystemet, så ingenting under
/proc dukker opp. For vide søk er det akkurat det du vil, og kapittelet
Disker og lagring
har mer om hva som teller som ett filsystem.
Vide søk krysser også mapper du ikke kan lese, og find rapporterer hver
enkelt på stderr. Sett opp én med modus 000
(mkdir ~/logs/locked; chmod 000 ~/logs/locked):
$ find ~/logs -name "*.tmp" -type f
find: ‘/home/user/logs/locked’: Permission denied
/home/user/logs/junk1.tmp
/home/user/logs/junk2.tmp
$ find ~/logs -name "*.tmp" -type f 2>/dev/null
/home/user/logs/junk1.tmp
/home/user/logs/junk2.tmp
Ordlyden er identisk på Ubuntu, Fedora og Arch, og 2>/dev/null demper
linjen. Å dempe er ikke å fikse: mappen er fortsatt uleselig, og find
avslutter fortsatt med exit status 1. Fjern veisperringen etterpå med
rmdir ~/logs/locked. Guiden
Feilsøking
behandler Permission denied som et symptom i seg selv.
Å gjøre noe med resultatene
Utskrift er standard, men find kan levere treffene i andre former. -ls
gir en oppføring i ls -dils-stil, én linje per treff:
$ find ~/logs/sizes -type f -ls
1185073 4 -rw-r--r-- 1 user user 1 Sep 7 10:31 /home/user/logs/sizes/one.txt
1185074 0 -rw-r--r-- 1 user user 0 Sep 7 10:31 /home/user/logs/sizes/empty.txt
1185075 1536 -rw-r--r-- 1 user user 1572864 Sep 7 10:31 /home/user/logs/sizes/big.bin
-exec CMD {} \; kjører CMD én gang per treff, med {} erstattet av
stien. De to avsluttende formene oppfører seg svært ulikt:
$ find ~/logs/sizes -type f -exec echo {} \;
/home/user/logs/sizes/one.txt
/home/user/logs/sizes/empty.txt
/home/user/logs/sizes/big.bin
$ find ~/logs/sizes -type f -exec echo {} +
/home/user/logs/sizes/one.txt /home/user/logs/sizes/empty.txt /home/user/logs/sizes/big.bin
Med \; kjørte find echo tre ganger, én gang per fil. Med + samlet den
alle tre stiene i ett kall, noe som lønner seg på store trefflister. En
nær slektning, -execdir, kjører kommandoen inne i hver fils egen mappe
med {} som ./basename, mens vanlig -exec alltid jobber i
startmappen: kjør find ~/logs/old -name "*.log" -exec pwd \;, og pwd
svarer med startmappen to ganger, én per treff.
Når kommandoen er destruktiv og du vil ha et ord med i hver enkelt sak,
virker -ok som -exec \;, men spør først:
$ find ~/logs -name "*.tmp" -ok rm {} \;
< rm ... /home/user/logs/junk1.tmp > ? y
< rm ... /home/user/logs/junk2.tmp > ? n
... i spørsmålet er find sin egen forkortelse for kommandoen, ikke en
skrivefeil. y som svar fjernet junk1.tmp, og n som svar lot junk2.tmp
ligge i fred, der den fortsatt ligger.
Å slette uten å angre
-delete fjerner matchede filer. Den trygge måten å bruke den på, er et
ritual: kjør kommandoen med -print først, les listen, og bytt så
-print ut med -delete. Én finurl: testene alene er ikke det du
trenger å forhåndsvise. Hele kommandoen, i nøyaktig den rekkefølgen, er
det som kjøres.
Lag en engangsmappe i /tmp og kopier den, slik at begge rekkefølgene
blir prøvd:
$ mkdir -p /tmp/trap-demo/sub
$ cd /tmp/trap-demo
$ touch a.txt b.txt data.csv keep.md t1.tmp t2.tmp sub/nested.txt sub/photo.jpg
$ cp -r /tmp/trap-demo /tmp/trap-demo2
$ find .
./a.txt
./b.txt
./data.csv
./keep.md
./sub
./sub/nested.txt
./sub/photo.jpg
./t1.tmp
./t2.tmp
Ni filer og mapper. Slett nå «txt-filene», skrevet slik folk naturlig skriver det, handling først:
$ find . -delete -name "*.txt"
$ find .
.
Alt er borte: csv-filen, markdown-filen, den nøstede tekstfilen og bildet
i sub/, de to tmp-filene, og sub-mappen selv, så bare startpunktet
står igjen. Kommandoen kjørte feilfritt. Det er problemet. -delete er
en handling, og denne kjeden leses venstre til høyre, så slettingen
skjedde før -name-testen i det hele tatt ble nådd, og alle stier
matchet. Mappen forsvant også, fordi -delete innebærer -depth: find
behandler innholdet før selve mappen, tømmer sub/ og fjerner den så.
Kopien overlevde til sammenligning:
$ cd /tmp/trap-demo2
$ find . -name "*.txt" -delete
$ find .
.
./data.csv
./keep.md
./sub
./sub/photo.jpg
./t1.tmp
./t2.tmp
Samme filer, test først, og bare de tre .txt-filene mangler. Tester
før handlinger, alltid. Når du forhåndsviser, forhåndsvis hele
kommandoen, og endre bare det siste ordet.
Å koble find sammen med andre kommandoer
Filnavn med mellomrom er der det opplagte pipe-oppsettet ryker, så sett opp én utenfor sandkassen:
$ mkdir /tmp/pipe-demo
$ touch "/tmp/pipe-demo/space test.log" /tmp/pipe-demo/plain.log
$ find /tmp/pipe-demo -name "*.log" -print0 | xargs -0 rm
$ find /tmp/pipe-demo -name "*.log"
En vanlig pipe ville ha delt space test.log i to argumenter, rm ville
ha feilet på begge halvdelene med exit status 123, og selve filen med
mellomrom i navnet ville ha overlevd urørt. -print0 avslutter hvert
resultat med en nullbyte i stedet for et linjeskift, og xargs -0 leser
dem tilbake på samme måte. Paret trengs når filnavn inneholder mellomrom
eller linjeskift, og er ufarlig ellers.
For å telle i stedet for å handle, piper du til wc -l:
find ~/logs -name "*.log" -type f | wc -l svarer 7 akkurat nå. Og leter
du etter noe som bor inne i filene i stedet for i navnene deres, er det
greps territorium: grep -rl "rotate" ~/logs lister filer hvis innhold
nevner «rotate», mens find aldri ser på annet enn navn og metadata.
Kapittelet
Tekstbehandling
dekker grep og resten av den verktøykassa.
Avslutningsprosjekt — rydd i en gammel loggmappe
Sandkassen ligner nå en ekte loggmappe: ni dager gamle logger på
toppnivå, en old/-mappe ingen rydder i, en cache/ som tilhører et
eller annet program, og en strøing av personlige filer. Målet: fjern de
gamle loggene, rør ingenting annet, og kunne bevise det.
Først en telling, med cache/ holdt utenfor, siden det som ligger der,
er programmets eget ærend:
$ find ~/logs -name cache -prune -o -type f -print | wc -l
13
Tretten filer utenfor cachen. Så kandidatene: logger eldre enn en uke, bare filer, skrevet ut til inspeksjon:
$ find ~/logs -name "*.log" -type f -mtime +7 -print
/home/user/logs/cache.log
/home/user/logs/debug.log
/home/user/logs/old/app.log
/home/user/logs/old/error.log
Fire filer, ingen av dem under cache/. Den ferske session.log der
inne er altfor ung til å matche, og old/keep.txt stryker på
navnetesten, så ingen av dem dukker opp. Den utskrevne listen er hele
slettesettet, for forhåndsvisning og sletting skiller seg med ett ord.
Les den én gang, og bytt så:
$ find ~/logs -name "*.log" -type f -mtime +7 -delete
$ find ~/logs -name "*.log" -type f
/home/user/logs/app.log
/home/user/logs/cache/session.log
/home/user/logs/space test.log
De fire gamle loggene er borte. Alt annet står fortsatt: de ferske
loggene, keep.txt, notatfilen, cachen, tmp-filen som -ok lot ligge
tidligere. Kjør tellingen på nytt, og den rapporterer 9. Samme ritual
skalerer til systemloggmapper når du har rettighetene der, og guiden
Filsystemet
forklarer hva som ligger hvor.
Hvor du står nå
Du kan lese enhver find-kommando som en kjede av tester og handlinger,
venstre til høyre, filtrere på navn, type, alder, størrelse, eier og
rettigheter, styre søket med -maxdepth, -prune og -xdev, og handle
på resultatene med -ls, -exec og -ok. Vanen som binder det sammen,
er forhåndsvisningsritualet, og minnet som redder en mappe en dag, er at
-delete er en handling, underlagt samme venstre-til-høyre-rekkefølge
som alt annet.
For hele alternativflaten er de offisielle referansene de du skal ha liggende åpne, alle på engelsk:
- GNU Findutils-manualen — den komplette offisielle dokumentasjonen
- find(1)-mansiden — hver test og handling i referanseform
- xargs(1)-mansiden — mottakerenden av
-print0