På denne siden

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: