Linux commando: du
Een schijf zit vol. Je draait df -h en die zegt 95 procent. Je draait du -sh / en de getallen kloppen niet, soms honderden gigabytes niet. Geen van beide commando's is kapot en geen van beide liegt. Ze beantwoorden twee verschillende vragen, en het gat tussen hun antwoorden is een van de nuttigste dingen die een Linux-beheerder kan begrijpen. Dit artikel gaat over wat du werkelijk telt, waarom dat niet is wat je aannam, en hoe je vindt wat je schijf volzette.
1. De basis
du loopt een mappenboom door en telt op hoeveel ruimte de bestanden erin innemen. Die zin bevat drie aannames die het uit elkaar halen waard zijn, want bij elk ervan gaat het mis.
1.1 De eenvoudigst mogelijke toepassing
Twee opties doen negentig procent van het werk, en je wilt ze bijna altijd allebei:
| Optie | Afkorting van | Wat het doet |
|---|---|---|
-s |
summarize | Een totaal per argument in plaats van een regel per map |
-h |
human-readable | K, M, G in plaats van een kaal aantal blokken |
$ du -sh /var/log
1.4G /var/log
Zonder -s krijg je een regel voor elke onderliggende map, en op een grote boom zijn dat duizenden regels die voorbijscrollen. Zonder -h krijg je een getal in blokken van 1024 bytes, en dat wil niemand uit het hoofd delen.
De nuttigste aanroep uit dit hele artikel is deze, en die is het onthouden waard:
$ du -h -d 1 /var | sort -h -r
8.2G /var
6.1G /var/lib
1.4G /var/log
403M /var/cache
...
-d 1 beperkt de uitvoer tot een niveau lager, en sort -h -r zet de grootste bovenaan. Draai dat, stap de grootste map in, draai het opnieuw. Drie of vier rondes en je hebt gevonden wat de schijf opat.
1.2 du telt blokken, geen bytes
Hier is de eerste aanname om los te laten. du meldt niet hoeveel bytes er in je bestanden zitten. Het meldt hoeveel ruimte het bestandssysteem toewees om ze te bewaren, en dat zijn verschillende getallen.
$ echo -n "x" > tiny.txt # a one-byte file
$ du --apparent-size -b tiny.txt
1 tiny.txt # one byte of content
$ du tiny.txt
4 tiny.txt # four KILOBYTES on disk
$ stat -c '%s bytes, %b blocks of %B' tiny.txt
1 bytes, 8 blocks of 512
Een bestandssysteem deelt ruimte uit in blokken, doorgaans 4096 bytes op ext4. Een bestand van een byte krijgt een heel blok, en 4095 bytes daarvan zijn verspild. Dat is geen fout, zo werkt blokindeling nu eenmaal, en het betekent dat een map met veel kleine bestanden veel meer kost dan de som van hun inhoud. Paragraaf 7.4 zet er getallen bij.
Nieuwere bestandssystemen maken dit in twee richtingen ingewikkelder. Extents, inline opslag voor heel kleine bestanden en tail packing kunnen alles strakker inpakken dan het platte blokmodel suggereert, terwijl compressie de toegewezen omvang kleiner kan maken dan de inhoud. De 4 kB-regel is een goed uitgangsmodel voor ext4 en een vertrekpunt elders, geen wet.
Nog een ding is een object met een eigen omvang: de map zelf. Die moet de namen bewaren die erin staan, dus hij kost ruimte zelfs als hij leeg is, en hij groeit terwijl je hem vult:
$ mkdir emptydir
$ du -h emptydir
4.0K emptydir # empty, and already 4 KB
$ du -h --apparent-size emptydir
0 emptydir # no content at all
$ for i in $(seq 1 3000); do : > emptydir/file-with-a-longish-name-$i; done
$ stat -c '%s bytes' emptydir
167936 # the directory itself is now 164 KB of names
du telt dus geen bestandsinhoud op. Het telt toegewezen ruimte op over elk object dat het langsloopt, mappen inbegrepen, en dat is nog een kleine reden dat het totaal niet gelijk is aan een som van bestandsgroottes.
1.3 du en df beantwoorden verschillende vragen
Dit is het onderscheid waar het hele artikel om draait. Zeg het een keer hardop en een hoop verwarring verdwijnt:
du | df | |
|---|---|---|
| Vraag | "Hoeveel ruimte gebruiken deze bestanden?" | "Hoeveel ruimte heeft dit bestandssysteem nog over?" |
| Methode | Loopt de boom door en telt elk bestand op dat het kan zien | Vraagt het bestandssysteem om een getal dat het al bijhoudt |
| Bereik | Een map | Een heel bestandssysteem |
| Snelheid | Traag: het moet naar alles kijken | Direct, hoe groot de schijf ook is |
| Blind voor | Verwijderde maar nog geopende bestanden, onleesbare mappen van anderen, alles wat onder een koppelpunt verborgen zit | Niets, maar het kan je niet vertellen welke bestanden |
Het snelheidsverschil is niet subtiel, en het volgt rechtstreeks uit de methode:
$ du -sxh /usr/share 0.68 s # 207,983 files, every one stat'ed
$ df -h / 0.00 s # one question to the filesystem
Naar bovenHet juiste mentale model:
dfvraagt het bestandssysteem hoeveel ruimte er over is.duvraagt de bestanden hoeveel ruimte zij innemen. Als die twee het oneens zijn, bestaat het verschil uit dingen dieduniet kan zien, en paragraaf 7.1 is een opsomming daarvan.
2. Waar komt de naam vandaan?
du is disk usage en df is disk free. Twee letters elk, in de stijl van een tijd waarin elk teken op een teletype getypt moest worden en zelfs de opslag voor de commandonaam zelf de moeite van het overwegen waard was.
De namen zijn stilletjes verschoven. Moderne coreutils zegt op geen van beide plekken meer "disk", en de twee beschrijvingen op je eigen machine zijn het niet eens met elkaar:
$ du --help | head -3 | tail -1
Summarize device usage of the set of FILEs, recursively for directories.
$ man du | sed -n '/^NAME/,+2p'
NAME
du - estimate file space usage
Beide wijzigingen zijn eerlijk. "Device usage" verving "disk usage" omdat wat eronder zit vaak geen schijf is: het kan een SSD zijn, een netwerkschijf, een tmpfs in het geheugen, of een virtueel volume. En de handleiding zegt estimate, en dat is het nuttigste woord in dit artikel. du geeft je een goed getal, geen waar getal, en de paragrafen 7.1 tot 7.3 zijn drie verschillende redenen waarom.
Het vocabulaire dat je nodig hebt is klein, en elk van deze termen veroorzaakt ergens in dit artikel een misverstand:
apparent size how many bytes the file contains
device usage how much space the filesystem allocated for it
block the smallest unit a filesystem hands out, usually 4096 bytes
sparse file a file with holes: apparent size much larger than device usage
hard link one file with two names; du counts the space once
inode the filesystem's record of a file; you can run out of these
reserved space df counts as used that only root may touch
Houd apparent size en device usage uit elkaar in je hoofd, want du meldt standaard het tweede terwijl ls -l het eerste toont. Paragraaf 6.2 laat een bestand zien waar ze oneindig veel schelen.
3. Een korte geschiedenis
Beide commando's zijn zo oud als Unix zelf. Niet "vroeg Unix": de allereerste uitgave.
| Tijdvak | Mijlpaal |
|---|---|
| 3 november 1971 | du en df verschijnen allebei in Version 1 AT&T Unix, op naam van Ken Thompson en Dennis Ritchie. |
| Begin jaren zeventig | Ze dateren van voor de herschrijving van Unix in C. Deze commando's zijn ouder dan de taal waarin het meeste van het systeem nu geschreven is. |
| Jaren tachtig | POSIX standaardiseert beide. -s, -a en -k komen uit dat tijdvak en werken overal. |
| Jaren negentig | GNU bouwt ze opnieuw voor coreutils, met -h, -d, --exclude, --apparent-size en de rest van wat dit artikel gebruikt. |
| Vanaf 2000 | Schijven groeien sneller dan du sneller wordt, en interactieve tools zoals ncdu verschijnen om de wandeling draaglijk te maken. |
| coreutils 9.x | De helptekst verandert "disk usage" in "device usage", een erkenning dat de opslag eronder vaak geen schijf is. |
De GNU-versies noemen hun auteurs, en de overlap tussen de twee lijsten is geen toeval:
$ man du | grep -A2 AUTHOR
Written by Torbjorn Granlund, David MacKenzie, Paul Eggert, and
Jim Meyering.
$ man df | grep -A2 AUTHOR
Written by Torbjorn Granlund, David MacKenzie, and Paul Eggert.
Een historisch detail duikt nog dagelijks op. De standaardeenheid is 1024 bytes, maar POSIX schrijft 512 voor, dus hetzelfde commando geeft je in een POSIX-strikte omgeving twee keer zo grote getallen:
$ du tiny.txt
4 tiny.txt
$ POSIXLY_CORRECT=1 du tiny.txt
8 tiny.txt # same file, 512-byte blocks
En POSIXLY_CORRECT is niet de enige variabele die meespeelt. Drie andere veranderen de eenheid ook, en du volgt degene die hij het eerst tegenkomt:
$ du -s target
20484 target # the default, 1024-byte blocks
$ DU_BLOCK_SIZE=1M du -s target
21 target
$ BLOCK_SIZE=1M du -s target
21 target
$ BLOCKSIZE=1M du -s target
21 target
$ POSIXLY_CORRECT=1 du -s target
40968 target # 512-byte blocks: twice the number
Een kaal getal van du hangt dus af van vier omgevingsvariabelen, die elk gezet kunnen zijn in een cron-omgeving, een CI-runner of iemands .bashrc. Daarom is -h, -k of -B in een script niet optioneel. Aan het getal hangt geen eenheid, en de eenheid wordt bepaald door een omgeving die je misschien niet beheert.
De andere overdraagbaarheidsvraag is welke opties er uberhaupt zijn. Het meeste van wat GNU du prettig maakt is een GNU-uitbreiding, en in een minimale container-image krijg je BusyBox. Getest tegen BusyBox 1.38:
| Optie | GNU coreutils | BusyBox 1.38 |
|---|---|---|
-s, -h, -a, -c, -x, -k, -m, -l, -L, -H |
Ja | Ja |
-d N |
Ja | Ja |
--max-depth=N |
Ja | Nee: unrecognized option |
--apparent-size |
Ja | Nee. Gebruik -b, dat beide hebben en dat de apparent size in bytes meldt |
--exclude, -X |
Ja | Nee |
--inodes |
Ja | Nee |
-t, -S, -B, --si |
Ja | Nee |
De -b-regel verdient een moment, want de twee schrijfwijzen zijn niet uitwisselbaar qua eenheid. Op een sparse bestand van 50 MB drukt du --apparent-size 51200 af (in blokken van 1 kB) terwijl du -b 52428800 afdrukt (bytes). GNU definieert -b als --apparent-size --block-size=1, en BusyBox doet hetzelfde, dus -b is de overdraagbare keuze zolang je bytes verwacht.
Let ook op de diepteregels, want die tellen voor wat je dagelijks typt. -d 1 werkt overal; --max-depth=1 niet. Ze doen precies hetzelfde, dus er is geen reden om de lange te leren. Daarom gebruikt elk voorbeeld in dit artikel -d.
$ docker run --rm busybox du --max-depth=1 /t
du: unrecognized option '--max-depth=1'
$ docker run --rm busybox du -d 1 /t
ok
macOS en de BSD's zijn weer een derde dialect, met hun eigen schrijfwijze voor een deel hiervan. Houd het op alles wat mogelijk geen GNU is bij -s, -h, -k, -x, -a en -d, en raadpleeg de lokale handleidingpagina voor de rest.
4. Eenvoudige toepassingen
4.1 De omvang van een map
$ du -sh /home/peter # one number
$ du -sh /home/* # one number per user
$ du -sh /var/www/*/ # one per site, the trailing slash skips files
Die tweede vorm is degene om naar te grijpen als een gedeelde server volloopt. Een regel per account, en de boosdoener is meestal meteen duidelijk.
4.2 De grote mappen vinden
-d (afkorting van max-depth) is wat du bruikbaar maakt op een grote boom. Vergelijk:
$ du -h sub # every directory, however deep
2.1M sub/a/b/c
4.1M sub/a/b
6.1M sub/a
8.1M sub
$ du -h -d 1 sub # only one level down
6.1M sub/a
8.1M sub
$ du -sh sub # the same as -d 0
8.1M sub
En sorteer dan. sort -h begrijpt de achtervoegsels K, M en G die du -h produceert, een betrekkelijk recent gemak en de reden dat deze combinatie uberhaupt werkt:
$ du -h -d 1 /var | sort -h -r | head -10
4.3 Bestanden meenemen
Standaard rapporteert du alleen mappen. -a (afkorting van all) voegt de bestanden toe:
$ du -ah sub | head -6
2.0M sub/a/f.bin
2.0M sub/a/b/c/f.bin
2.1M sub/a/b/c
2.0M sub/a/b/f.bin
4.1M sub/a/b
6.1M sub/a
Gecombineerd met sort vindt dit de grootste losse bestanden in plaats van de grootste mappen:
$ du -ah /var/log | sort -h -r | head -10
4.4 Totalen
-c (afkorting van total) voegt een eindtotaalregel toe:
$ du -shc /home/*/Downloads
2.3G /home/anna/Downloads
891M /home/peter/Downloads
3.2G total
Handig als de vraag is "hoeveel krijg ik terug door dit allemaal leeg te maken", wat een andere vraag is dan "welke is de grootste".
Naar boven5. Gemiddelde toepassingen
5.1 Dingen uitsluiten
--exclude neemt een patroon en slaat alles over wat erop past. Dat telt zwaarder dan het klinkt, want op een echte server is het interessante getal meestal "hoe groot is dit als ik de delen negeer die ik al ken":
$ du -sh /var/www/site --exclude='*/cache'
$ du -sh /home/peter --exclude='.cache' --exclude='.local/share/Trash'
$ du -sh /srv/project --exclude='node_modules' --exclude='.git'
Bij meer dan twee of drie patronen zet je ze in een bestand en gebruik je -X:
$ cat skip.txt
node_modules
.git
*.iso
$ du -sh -X skip.txt /srv/project
5.2 Drempels
-t (afkorting van threshold) verbergt alles onder een bepaalde omvang, en dat maakt van een muur uitvoer een korte lijst:
$ du -h -d 3 -t 1G /var # only things over a gigabyte
$ du -ah -t 100M /home/peter # only files over 100 MB
Een negatieve drempel keert het om, en dat is de minder voor de hand liggende helft en af en toe precies goed:
$ du -ah -t -1k /srv/data # only entries SMALLER than 1K
Zo vind je de map met honderdduizend piepkleine bestanden, en paragraaf 7.4 legt uit dat dat een echt probleem is en niet alleen slordigheid.
5.3 Diepte zonder optelling: -S
-S (afkorting van separate-dirs) rapporteert de eigen inhoud van elke map zonder de submappen erbij op te tellen. Vergelijk met de standaard:
$ du -h sub $ du -Sh sub
2.1M sub/a/b/c 2.1M sub/a/b/c
4.1M sub/a/b 2.1M sub/a/b
6.1M sub/a 2.1M sub/a
8.1M sub 2.1M sub
De linkerkolom telt op; de rechter niet. Gebruik -S als je wilt weten welke enkele map de bestanden bevat, en niet welke tak van de boom het zwaarst is. Dat zijn verschillende vragen en mensen kiezen vaak de verkeerde.
5.4 Bestanden tellen in plaats van bytes
Een bestandssysteem kan aan twee dingen tekortkomen: ruimte, en inodes. Raakt het door zijn inodes heen, dan krijg je "No space left on device" terwijl df -h vrolijk meldt dat er genoeg vrij is. --inodes vindt de boosdoener met dezelfde wandeling:
$ du -sh many
12K many # 500 empty files: almost no space
$ du -s --inodes many
501 many # but 501 inodes
$ du --inodes -d 1 /var | sort -n -r | head
Controleer of dat je probleem is voordat je op jacht gaat naar grote bestanden:
$ df -i / # -i for inodes
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/mapper/... 120M 14M 106M 12% /
Mailwachtrijen, sessiemappen en caches met kleine bestanden zijn de gebruikelijke verdachten. Honderdduizend bestanden van een byte gebruiken vrijwel geen ruimte en honderdduizend inodes.
5.5 Het recept
Bij elkaar is dit de volgorde voor "de schijf zit vol en ik weet niet waarom":
$ df -h # 1. which filesystem, and is it really full?
$ df -i # 2. or has it run out of inodes instead?
$ du -shx /* 2>/dev/null | sort -h -r | head # 3. top level
$ du -h -d 1 -x /var | sort -h -r | head # 4. descend into the winner
$ du -h -d 1 -x /var/lib | sort -h -r | head # 5. and again
$ du -ah -t 100M /var/lib/docker | sort -h -r # 6. finally, the files
Stap 1 en 2 kosten een seconde en sluiten de twee problemen uit die van een afstand identiek lijken. Stap 3 tot 6 zijn hetzelfde commando met telkens een ander pad. Paragraaf 6.1 legt de -x uit.
6. Gevorderde toepassingen
6.1 Op een bestandssysteem blijven
-x (afkorting van one-file-system) zegt du niet over te steken naar een ander bestandssysteem. Zonder die optie loopt een wandeling van / elke koppeling in die je hebt: netwerkschijven, snap-loopbacks, de FUSE-koppelingen die je bureaubladsessie aanmaakte, en /proc.
Dat is geen theoretisch netheidsargument. Op deze machine zitten er alleen al veertien koppelingen onder /run:
$ findmnt -rno TARGET,FSTYPE | grep '^/run/'
/run/lock tmpfs
/run/snapd/ns tmpfs
/run/user/1000 tmpfs
/run/user/1000/doc fuse.portal
/run/user/1000/gvfs fuse.gvfsd-fuse
...
$ du -sxh /run
5.4M /run # finished instantly
$ timeout 20 du -sh /run
(timed out at 20 seconds) # wandered into the FUSE mounts and stalled
Een FUSE-koppeling kan een server op afstand zijn, een telefoon via MTP, of een clouddrive. du heeft geen manier om te weten dat elk bestand lezen een netwerkronde betekent, dus hij blijft gewoon zitten. Gebruik -x bij alles op of vlak bij de wortel van de boom, en merk op dat het natuurlijk samengaat met df, dat ook per bestandssysteem werkt.
6.2 Apparent size versus device usage
Paragraaf 1.2 liet een klein bestand zien dat meer ruimte gebruikt dan het bevat. Sparse bestanden zijn hetzelfde idee de andere kant op, en de getallen zijn spectaculair:
$ dd if=/dev/zero of=sparse.bin bs=1 count=0 seek=100M # a 100 MB hole
$ ls -lh sparse.bin
-rw-rw-r-- 1 peter peter 100M ... sparse.bin # ls sees 100 MB
$ du -h sparse.bin
0 sparse.bin # du sees nothing
$ du -h --apparent-size sparse.bin
100M sparse.bin # apparent size agrees with ls
Het bestand beweert 100 MB te zijn en bezet helemaal geen blokken, omdat er niets in geschreven is. Het bestandssysteem legt vast "dit bereik is nul" in plaats van honderd megabyte nullen te bewaren.
Dit is niet exotisch. Schijfimages van virtuele machines, databasebestanden, containerlaagbestanden en logbestanden die vooraf gereserveerd zijn, zijn routinematig sparse. Het heeft een praktisch gevolg: een sparse bestand kopieren kan het enorm maken, want een naieve kopie schrijft alle nullen uit. cp --sparse=always en rsync -S bewaren de gaten; tar heeft -S nodig.
6.3 Hard links worden een keer geteld
Staat hetzelfde bestand twee keer in een boom onder twee namen, dan is du slim genoeg om de ruimte een keer te tellen:
$ ln hl/orig.bin hl/link.bin # a hard link, not a copy
$ du -sh hl
11M hl # counted once
$ du -slh hl # -l for count-links
21M hl # counted twice
De standaard klopt, en het is wat du betrouwbaar maakt op backupbomen die met hard links gebouwd zijn, en zo werken rsync --link-dest en backups in Time Machine-stijl. Tien dagelijkse momentopnamen van dezelfde data nemen werkelijk ongeveer de ruimte van een in, en du vertelt je dat ook.
De valkuil zit aan de randen. du ontdubbelt alleen binnen een enkele run. Meet twee mappen in twee aparte commando's en het gedeelde bestand wordt in beide geteld, dus de totalen tellen niet op tot wat het bestandssysteem toont.
6.4 Symbolische links worden niet gevolgd
Hard links en symbolische links zijn verschillende dingen en du behandelt ze verschillend. Een hard link is nog een naam voor dezelfde data, dus du telt die een keer. Een symbolische link is een piepklein bestand met een pad erin, dus standaard telt du de link en kijkt hij nooit naar waar die heen wijst:
$ ln -s ../target tree/link # target holds 20 MB
$ du -sh tree
4.0K tree # the link itself, and nothing more
$ du -sh -L tree
21M tree # -L follows every symlink
$ du -sh -D tree
4.0K tree # -D follows only links NAMED on the command line
$ du -sh -D tree/link
21M tree/link # ... and now it is named
Vier opties bepalen dit, en de standaard is de veilige:
| Optie | Gedrag |
|---|---|
-P |
Volg niets. De standaard, en meestal wat je wilt. |
-L |
Volg elke symlink die ergens in de boom staat. |
-D (of -H) |
Volg alleen symlinks die je op de opdrachtregel noemt. |
-l |
Niet verwant: tel hard links herhaaldelijk. Makkelijk te verwarren met -L. |
De standaard klopt voor de gebruikelijke vraag, "hoeveel ruimte neemt deze map in?", want het doel van een symlink zit niet in de map en de map verwijderen zou die ruimte niet vrijmaken. Grijp alleen naar -L als je bedoelt "hoeveel data is vanaf hier bereikbaar", en wees voorzichtig: een symlink kan overal heen wijzen, dus -L kan een ander bestandssysteem in dwalen, een link terug naar een bovenliggende map volgen en heel lang duren, of data dubbel tellen die je al gemeten hebt.
-D is de tussenweg en degene om te gebruiken als je bewust een symlink als argument meegeeft: du -shD /var/www/current, waarbij current een uitrol-symlink is, meet de release waar hij naar wijst, zonder links te volgen die erbinnen staan.
6.5 Eenheden
Drie manieren om het getal te sturen, en een ervan verrast mensen:
$ du -h k.bin # powers of 1024
980K k.bin
$ du --si k.bin # powers of 1000
1.1M k.bin
$ du -B M k.bin # a unit you choose
1M k.bin
$ du -k k.bin ; du -m k.bin # 1K and 1M blocks, POSIX-portable
Dat is drie keer hetzelfde bestand van 1.000.000 bytes. -h deelt door 1024 en komt op 980K; --si deelt door 1000 en komt op 1,1M. Schijffabrikanten gebruiken de tweede afspraak, en dat is grotendeels waarom een schijf van "1 TB" als 931 GB verschijnt.
6.6 In scripts
Twee regels maken du veilig in een script. Leg altijd de eenheid vast, en ga altijd netjes met de bestandsnamen om:
# fix the unit: never rely on the default
size_kb=$(du -sk "$dir" | cut -f1)
if [ "$size_kb" -gt 1048576 ]; then echo "$dir is over 1 GB"; fi
# handle any filename, including newlines and spaces
find /srv -type d -name node_modules -print0 | du -sh --files0-from=- -c | tail -1
De uitvoer is een omvang, een enkele tab, en het pad, dus cut -f1 is de juiste manier om het getal te pakken, niet awk '{print $1}', dat breekt op een pad met spaties.
Een waarschuwing die paragraaf 7.2 uitwerkt: du schrijft zijn fouten naar standaardfout en zijn totaal naar standaarduitvoer, en het totaal bevat niets wat hij niet kon lezen. Een script dat standaardfout weggooit krijgt een zelfverzekerd getal dat niet klopt.
7. Iets wat de meeste gebruikers niet weten
7.1 Waarom df en du het oneens zijn
Dit is de vraag die mensen naar dit commando brengt, en het antwoord is niet een ding. Het zijn er vijf, ze stapelen op, en de laatste geldt alleen voor sommige bestandssystemen.
Gereserveerde blokken. Begin met df die het met zichzelf oneens is. Op deze machine tellen zijn eigen drie kolommen niet op:
$ df --block-size=1 /
size = 2011859927040
used = 1808341385216
avail = 101246103552
used + avail = 1909587488768
missing = 102272438272 bytes, which is 5.1% of size
Ongeveer vijf procent van een ext4-bestandssysteem is standaard gereserveerd voor root. Het is geen vrije ruimte wat jou betreft, en het wordt door geen enkel bestand gebruikt, dus het hoort in geen van beide kolommen. Daarom kan een schijf "100% vol" melden terwijl er technisch nog enkele gigabytes zijn. Je kunt het terugnemen op een datavolume, waar niets die veiligheidsmarge nodig heeft:
$ sudo tune2fs -m 1 /dev/sdb1 # reduce the reserve from 5% to 1%
Doe dat niet op het rootbestandssysteem. De reserve bestaat zodat een volle schijf root niet belet in te loggen en syslog niet belet te schrijven, en dat is precies wanneer je beide nodig hebt.
Verwijderde bestanden die nog openstaan. Dit is de grote, en het is de reden voor de meeste spectaculaire verschillen. Een bestand ontkoppelen haalt de naam weg; de ruimte komt pas terug als het laatste proces dat het open heeft loslaat:
$ dd if=/dev/zero of=ghost.bin bs=1M count=300
$ exec 9< ghost.bin # hold it open
$ rm ghost.bin # the name is gone
$ du -sh .
11M . # du sees nothing: there is no name to walk
df used before rm : 1724868 MB
df used after rm : 1724868 MB # df has not moved
df after closing : 1724568 MB # 300 MB back, at once
Een logbestand dat verwijderd wordt terwijl de applicatie het nog open heeft is de klassieke versie. De schijf blijft vol tot je de dienst herstart, en geen enkele du laat je dat bestand zien. Zo vind je ze:
$ sudo lsof -nP +L1 # +L1: link count below 1, meaning deleted
$ sudo lsof -nP | grep deleted
De oplossing is het proces herstarten dat de descriptor vasthoudt, of via /proc afkappen als dat niet kan: : > /proc/PID/fd/N.
Bestanden verborgen onder een koppelpunt. Zaten er bestanden in een map voordat er iets overheen gekoppeld werd, dan bestaan die nog steeds en nemen ze nog steeds ruimte in. Ze zijn alleen onbereikbaar, dus du kan ze niet tellen:
$ sudo mkdir /mnt/tmp-check
$ sudo mount --bind / /mnt/tmp-check # see the real root, unobscured
$ sudo du -shx /mnt/tmp-check/var # count what is actually there
$ sudo umount /mnt/tmp-check
Deze treft mensen die een map vulden voordat ze er een schijf op koppelden, en dat is makkelijk te doen tijdens een installatie en daarna onzichtbaar.
Overhead van het bestandssysteem. Inodetabellen, journalen en metagegevens nemen ruimte in die bij geen enkel bestand hoort. Op een groot bestandssysteem zijn dat gigabytes, en het verklaart een blijvend klein gat in plaats van een plotseling gat.
En op een modern bestandssysteem: de boekhouding zelf. Alles hierboven gaat uit van ext4, waar een blok bij precies een bestand hoort. Btrfs, ZFS en XFS breken die aanname op manieren die de vraag moeilijker maken in plaats van het antwoord fout:
| Eigenschap | Wat het met de getallen doet |
|---|---|
| Momentopnamen | Oude versies van bestanden bezetten nog blokken. Geen pad leidt ernaartoe, dus du ziet er geen byte van. Op een server met snapshots is dit meestal het grootste deel van het gat. |
| Reflinks en gedeelde extents | Twee bestanden kunnen dezelfde fysieke blokken delen. Beide lijken voor du volledig, en een ervan verwijderen maakt niets vrij. |
| Compressie | Het bestand bezet minder blokken dan zijn inhoud. du kan minder melden dan de apparent size op een gewoon, niet-sparse bestand. |
| Copy-on-write | Een deel van een bestand overschrijven wijst nieuwe blokken toe voordat de oude vrijkomen, dus het verbruik kan stijgen terwijl bestandsgroottes niet veranderen. |
Deze machine draait ext4, waar die dingen niet spelen: cp --reflink=always faalt hier met "Operation not supported". Maar op Btrfs of ZFS zijn ze doorgaans de voornaamste oorzaak van een gat tussen df en du, en geen enkel gereedschap dat paden afloopt kan ze verantwoorden. Gebruik dan het gereedschap van het bestandssysteem zelf: btrfs filesystem usage, btrfs qgroup show, of zfs list -o space.
Bij elkaar: df telt alles wat het bestandssysteem heeft vastgelegd. du telt wat het kan bereiken door namen af te lopen. Het gat is gereserveerde ruimte, plus bestanden zonder naam, plus bestanden zonder pad, plus de eigen boekhouding van het bestandssysteem, plus wat jouw bestandssysteem achter de schermen deelt of comprimeert.
7.2 Standaardfout weggooien laat du liegen
Iedereen schrijft 2>/dev/null om de rechtenwaarschuwingen het zwijgen op te leggen. Kijk wat dat met het antwoord doet:
$ du -sh /root 2>/dev/null
4.0K /root # looks like an empty directory
$ du -sh /root
du: cannot read directory '/root': Permission denied
4.0K /root # same number, now with a warning
Die 4.0K is de mapregel zelf. /root bevat op deze machine een map Desktop en een map snap, en geen van beide zit in het totaal. Het getal is geen fout, het is een eerlijk verslag van wat du kon zien, en het deel dat je vertelde dat hij niet alles kon zien ging naar standaardfout.
Dus als je du -shx /* 2>/dev/null als gewone gebruiker draait, behandel elk getal dan als een ondergrens. Draai het met sudo als het antwoord ertoe doet, en lees de waarschuwingen als dat niet zo is.
7.3 du telt overlappende argumenten niet dubbel
Geef du twee paden waarvan het ene het andere bevat, en hij is slimmer dan je zou denken:
$ du -sh sub/a
6.1M sub/a
$ du -sh sub/a/b
4.1M sub/a/b
$ du -sh sub/a sub/a/b -c
6.1M sub/a
6.1M total # not 10.2M
Hij houdt bij wat hij binnen een enkele run al bezocht heeft en weigert dezelfde blokken twee keer op te tellen. Dat maakt du -shc /home/* betrouwbaar, en het is hetzelfde mechanisme dat hard links in paragraaf 6.3 afhandelt.
De beperking is er ook dezelfde: dit geldt alleen binnen een aanroep. Twee losse du-commando's weten niets van elkaar.
7.4 Kleine bestanden kosten meer dan ze wegen
Paragraaf 1.2 liet een bestand van een byte zien dat 4 kB gebruikt. Vermenigvuldig dat met een echte map en het houdt op een curiositeit te zijn:
100,000 files of 100 bytes each
apparent size: 100,000 x 100 = 10 MB
device usage: 100,000 x 4096 = 410 MB
Veertig keer groter. Een mailspool, een map met PHP-sessies, een cache met miniaturen of een Git-objectopslag kan een orde van grootte meer ruimte innemen dan de data erin, en du --apparent-size naast een kale du vertelt je meteen of dat is waar je naar kijkt:
$ du -sh /var/lib/php/sessions
$ du -sh --apparent-size /var/lib/php/sessions
# a big gap between these two means "many small files", not "much data"
En denk aan paragraaf 5.4: diezelfde map verbruikt ook een inode per bestand, en dat is een voorraad die je los kunt uitputten.
7.5 du is traag omdat het niet anders kan
Er is geen sluiproute. Om een map op te tellen moet du elk bestand erin stat-en, want geen enkel gangbaar bestandssysteem houdt een lopend totaal per map bij. Daarom schaalt de kosten met het aantal bestanden en niet met hun omvang:
$ find /usr/share -xdev | wc -l
207983
$ du -sxh /usr/share 0.68 s (cold)
$ du -sxh /usr/share 0.28 s (warm, from the kernel's cache)
$ df -h / 0.00 s
Twee praktische gevolgen. du draaien op een grote boom warmt de cache op, dus een tweede run is veel sneller en elke meting na de eerste is te optimistisch. En op een drukke productieserver is een volledige du / niet gratis: het verdringt nuttige gegevens uit de cache en veroorzaakt veel metadata-I/O. Kies -x, kies een smal beginpunt, en overweeg ionice -c3 als het onder werktijd moet.
7.6 Weten waar du ophoudt
| Als je nodig hebt | Grijp naar |
|---|---|
Interactief verkennen in plaats van du opnieuw draaien |
ncdu: een wandeling, daarna bladeren en verwijderen |
| Een leesbaar overzicht van alle bestandssystemen | duf, of gewoon df -hT |
| Een grafische weergave | baobab (Schijfgebruik-analyse) |
| Bestanden op leeftijd of naam, niet alleen op omvang | find met -size, -mtime, -printf |
| Ruimte van verwijderde maar geopende bestanden | lsof -nP +L1, want du is er blind voor |
| Grenzen per gebruiker in plaats van rapportages per gebruiker | Quota van het bestandssysteem: quota, repquota |
| Trends over tijd | Monitoring, geen commando. du heeft geen geheugen. |
ncdu verdient de bovenste regel en is het overal installeren waard. Het doet dezelfde wandeling als du, een keer, en geeft je daarna een interactieve boom waarin je kunt bewegen en verwijderen, waarmee de afdaling in vier rondes uit paragraaf 5.5 pijltjestoetsen wordt.
8. Best practices
- Leer een commando goed:
du -h -d 1 MAP | sort -h -r. Draai het, stap de grootste regel in, herhaal. Dat vindt vrijwel alles in drie of vier rondes. - Voeg
-xtoe zodra je bij de wortel begint. Zonder die optie dwaaltdunetwerkschijven en FUSE-koppelingen in en kan hij voorgoed blijven hangen. - Controleer eerst
df -hendf -i. Vol met data en zonder inodes zien er vanaf de applicatiekant identiek uit en vragen om compleet verschillende oplossingen. - Vertrouw geen totaal waarvan de standaardfout is weggegooid. Elke onleesbare map ontbreekt er stilletjes in. Gebruik
sudoals het antwoord ertoe doet. - Leg de eenheid vast in scripts.
du -skofdu -B M, nooit een kaledu, want vier verschillende omgevingsvariabelen kunnen veranderen wat het getal betekent. - Ontleed met
cut -f1, niet metawk '{print $1}'. Het scheidingsteken is een enkele tab, en paden bevatten spaties. - Vergelijk
du -shmetdu -sh --apparent-sizeals een getal er vreemd uitziet. Een groot verschil betekent veel kleine bestanden of een sparse bestand, en die twee vragen om tegengestelde reacties. - Sluit uit wat je al weet.
--exclude='node_modules'en soortgenoten maken van een nutteloos totaal een informatief totaal. - Gebruik
-tom de ruis weg te snijden in plaats van doorheadte sturen en te hopen. - Zijn
dfenduhet flink oneens, zoek dan eerst naar verwijderde maar geopende bestanden.sudo lsof -nP +L1, en herstart daarna wat ze vasthoudt. - Installeer
ncduop servers die je beheert. Een wandeling, daarna een interactieve boom, en je kunt er vanuit verwijderen. - Wees rekening houdend op productie. Een volledige
du /veroorzaakt zware metadata-I/O en verdringt de cache. Maak het pad smaller, gebruik-x, en overweegionice -c3. - Kies
-d 1boven--max-depth=1. Identiek gedrag, en de korte vorm werkt ook op BusyBox, en dat is wat je in een minimale container krijgt. - Laat het volgen van symlinks met rust tenzij je het bedoelt. De standaard telt de link en stopt, en dat is juist voor "hoe groot is deze map".
-Lis voor "hoeveel is vanaf hier bereikbaar" en kan afdwalen. - Grijp op een bestandssysteem met snapshots naar zijn eigen gereedschap.
btrfs filesystem usageenzfs list -o spacezien watduniet kan zien. - Lees de handleiding een keer. Hij is kort, en het woord "estimate" in de allereerste regel is het eerlijkste dat erin staat.
$ man 1 du # short; note the NAME line
$ man 1 df # its counterpart, read them together
$ info coreutils 'du invocation'
$ man 1 ncdu
$ man 8 lsof # for the deleted-but-open case
Naar boven9. Veelgemaakte fouten
9.1 Mythe versus werkelijkheid
| Mythe | Werkelijkheid |
|---|---|
"du en df horen het eens te zijn." |
Ze beantwoorden verschillende vragen. Het gat bestaat uit gereserveerde blokken, verwijderde maar geopende bestanden, bestanden onder koppelpunten, metagegevens, en op Btrfs of ZFS ook snapshots en gedeelde extents (paragraaf 7.1). |
"du vertelt me hoeveel bytes mijn bestanden bevatten." |
Het vertelt je hoeveel blokken ze bezetten. Een bestand van een byte meldt 4 kB. Gebruik --apparent-size voor de inhoud. |
"ls -lh en du -h horen overeen te komen." |
ls toont apparent size, du toont device usage. Op een sparse bestand is dat 100M tegen 0. |
| "De schijf is vol, dus ik moet grote bestanden verwijderen." | Misschien. Controleer eerst df -i: je kunt door je inodes heen zijn, en dan moet je veel bestanden verwijderen, geen grote. |
| "Ik heb het log verwijderd, dus de ruimte is terug." | Niet zolang een proces het open houdt. df beweegt pas als je de dienst herstart (paragraaf 7.1). |
"du -sh /* 2>/dev/null laat me alles zien." |
Het laat zien wat jouw gebruiker mag lezen. De mappen waarvoor hij geweigerd werd ontbreken stilletjes in de totalen (paragraaf 7.2). |
"du -sh a b telt dubbel als b in a zit." |
Nee. du houdt bij wat hij binnen een run bezocht heeft (paragraaf 7.3). |
| "Backups met hard links gebruiken tien keer zoveel ruimte." | du telt gedeelde blokken een keer, dus tien momentopnamen kosten werkelijk ongeveer een. -l zet die ontdubbeling uit. |
"du is traag omdat de map groot is." |
Hij is traag door het aantal bestanden. Hij moet er elk stat-en; er is geen opgeslagen totaal om te lezen. |
"du -h en du --si zijn hetzelfde." |
1024 tegen 1000. Hetzelfde bestand is 980K of 1,1M, afhankelijk van welke je kiest. |
"Een kaal getal van du is in kilobytes." |
Meestal, maar het zijn blokken van 512 bytes onder POSIXLY_CORRECT. Geef altijd -k, -h of -B mee. |
| "Een bestand kopieren kan het niet groter maken." | Een sparse bestand dat naief gekopieerd wordt schrijft al zijn gaten uit. Gebruik cp --sparse=always of rsync -S. |
"du volgt symlinks naar de mappen waar ze heen wijzen." |
Nee. De standaard is -P: hij telt de link, 4.0K, en stopt. -L volgt ze wel, -D volgt alleen die je noemt (paragraaf 6.4). |
"-l en -L hebben met elkaar te maken." |
Nee. -l telt hard links herhaaldelijk; -L volgt symbolische links. Twee verschillende problemen, een letter uit elkaar. |
"Een kale du is in kilobytes tenzij POSIXLY_CORRECT gezet is." |
Vier variabelen veranderen het: DU_BLOCK_SIZE, BLOCK_SIZE, BLOCKSIZE en POSIXLY_CORRECT. Geef altijd een eenheid mee (paragraaf 3). |
"--max-depth=1 is de overdraagbare schrijfwijze." |
Het is de GNU-only schrijfwijze. BusyBox weigert hem en accepteert -d 1, dat overal hetzelfde doet. |
"du kan me altijd vertellen wat verwijderen zou vrijmaken." |
Niet met snapshots, reflinks of gedeelde extents, waar de blokken bij meer dan een ding horen. Gebruik het gereedschap van het bestandssysteem (paragraaf 7.1). |
9.2 Andere valkuilen om te vermijden
du /zonder-xdraaien op een bureaublad. Hij daalt af in/run/user/1000/gvfsen wat dat ook koppelt, en komt misschien nooit terug.- Uitvoer van
dulezen zonder te sorteren. De volgorde is die van de mapwandeling, niet die van de omvang.sort -h -ris niet optioneel. -hvergeten aan een kant van een pipe.sort -hheeft de achtervoegsels nodig diedu -hproduceert.du -k | sort -nwerkt ook; de twee mengen niet.awk '{print $1}'gebruiken om de omvang eruit te halen. Werkt tot een pad een spatie bevat. Het scheidingsteken is een tab, duscut -f1is juist.- Verwijderen vanuit
ncduzonder te kijken. Het verwijdert direct en definitief, zonder prullenbak en zonder ongedaan maken. -Lgebruiken zonder na te denken over waar de links heen gaan. Een symlink kan buiten de boom wijzen, naar een ander bestandssysteem, of terug naar een bovenliggende map.-Lvolgt ze allemaal, dus een scan waarvan je een seconde verwachtte kan ver afdwalen.dude schuld geven op een Btrfs- of ZFS-server. Bestaan er snapshots, dan zit de ontbrekende ruimte daarin en vindt geen enkel gereedschap dat paden afloopt die.btrfs filesystem usageofzfs list -o spacebeantwoorden de vraag diedustructureel niet kan.- Aannemen dat
duop een netwerkschijf snel is. Elke stat is een ronde over het netwerk. Een boom die lokaal twee seconden kost kan over NFS of SMB twintig minuten duren. - Totalen van
duuit twee aparte runs vergelijken. Hard links worden alleen binnen een run ontdubbeld, dus twee runs over bomen die bestanden delen tellen ertussen dubbel. - Een totaal vertrouwen dat genomen is terwijl er geschreven wordt.
duis niet atomair. Op een actieve server is het getal een uitsmering over de tijd die de wandeling kostte. - Ruimte vrijmaken door te verwijderen in een map waar iets overheen gekoppeld is. Je verwijdert dan uit het gekoppelde bestandssysteem, niet uit het bestandssysteem waarvan de bestanden eronder verborgen zitten.
- Een bestandssysteem tot precies 100 procent vullen. Zodra ook de rootreserve op is, falen diensten die hun logs of statusbestanden niet kunnen schrijven op verwarrende manieren. Houd marge.
10. Samenvatting
du en df kwamen allebei uit op 3 november 1971 en mensen zijn sindsdien in de war over het verschil. Die verwarring is het een keer opruimen waard, want het maakt van een volle schijf een checklist in plaats van een raadsel.
Onder het hele artikel ligt een idee dat het duidelijk uitspreken waard is. "Hoe groot is dit?" is niet een vraag. Een bestand heeft minstens vijf groottes, en ze kunnen alle vijf tegelijk verschillen:
apparent size how many bytes it contains ls -l, du --apparent-size
allocated blocks what the filesystem gave it du
physical space after compression or deduplication filesystem-specific tools
shared blocks it holds in common with others reflinks, snapshots
reclaimable what deleting it would actually free none of the above
Op een gewoon ext4-bestand liggen de eerste twee dicht bij elkaar en spelen de rest niet, en daarom voelt de vraag meestal eenvoudig. Een sparse image, een volume met snapshots of een backupboom met hard links trekt ze uit elkaar, en dan is het eerlijke antwoord op "hoe groot is het" een wedervraag: welke daarvan bedoelde je? du beantwoordt de tweede regel, goed en snel, en dat weten is het grootste deel waar dit artikel voor bedoeld is.
dfvraagt het bestandssysteem hoeveel ruimte er over is.duvraagt de bestanden hoeveel ruimte zij innemen. Verschillende vragen, verschillende antwoorden, en allebei juist.- Leer een regel:
du -h -d 1 MAP | sort -h -r. Daal af in de grootste regel en herhaal. dumeldt blokken, geen bytes. Een bestand van een byte kost 4 kB, en 100.000 kleine bestanden kunnen 410 MB kosten om 10 MB data te bewaren.--apparent-sizegeeft in plaats daarvan de inhoud. Op een sparse bestand zegtls100M, zegtdu0, en hebben beide gelijk.- Gebruik
-xbij de wortel. Zonder die optie liepdu -sh /runop deze machine een FUSE-koppeling in en was hij na twintig seconden nog niet klaar;du -sxh /runantwoordde direct. - Controleer ook
df -i. Door je inodes heen ziet er precies zo uit als door je ruimte heen, en vraagt om de tegenovergestelde oplossing: verwijder veel bestanden, geen grote.du --inodesvindt ze. - Zijn de twee het oneens, dan bestaat het gat op ext4 uit vier dingen: de rootreserve van 5 procent (hier gemeten op 5,1 procent), verwijderde maar geopende bestanden, bestanden verborgen onder een koppelpunt, en metagegevens van het bestandssysteem.
- Verwijderd maar geopend is de grote: 300 MB bleef in gebruik na
rmen kwam terug op het moment dat de descriptor sloot. Vind ze metsudo lsof -nP +L1. 2>/dev/nulllaatduliegen. Onleesbare mappen ontbreken stilletjes in het totaal, dusdu -sh /rootmeldt 4.0K voor een map met inhoud.dutelt hard links een keer en telt overlappende argumenten niet dubbel, maar alleen binnen een enkele run.- Hij is traag omdat hij elk bestand moet stat-en: 207.983 bestanden kostten 0,68 s, waar
df0,00 s kost bij elke schijfgrootte. - Leg de eenheid vast in scripts (
-skof-B M) en ontleed metcut -f1, want het scheidingsteken is een tab en de standaardeenheid verandert onderPOSIXLY_CORRECT. - Symlinks worden standaard niet gevolgd, en dat klopt: 4.0K voor de link tegen 21M met
-L.-Dvolgt alleen die je noemt. Verwar-l(hard links) niet met-L(symbolische). - Mappen kosten ook ruimte: 4.0K leeg, en 164 kB zodra ze 3000 namen bevatten.
- Voeg op Btrfs of ZFS een vijfde oorzaak toe aan het gat: snapshots, reflinks, compressie en copy-on-write. Geen enkel gereedschap dat paden afloopt kan die zien; gebruik het gereedschap van het bestandssysteem.
- Schrijf
-d 1, niet--max-depth=1. Zelfde resultaat, en BusyBox weigert de lange vorm. - Installeer
ncdu. Een wandeling, daarna een interactieve boom waaruit je kunt verwijderen.
Dit is het overzicht dat het bewaren waard is:
THE ONE TO REMEMBER
du -h -d 1 DIR | sort -h -r biggest first, one level down
du -sh DIR a single total
du -sh /home/* one total per entry
du -ah DIR | sort -h -r | head the biggest FILES, not directories
CONTROLLING THE WALK
-x stay on one filesystem <- USE THIS near /
-d N only N levels deep
-S each dir WITHOUT its subdirectories
-a include files, not just directories
--exclude=PATTERN skip matching entries -X FILE for a list
-t 1G / -t -1k only bigger / only smaller than
SYMLINKS (the default is right; -l is NOT -L)
-P follow nothing (the default)
-L follow every symlink (can wander far)
-D / -H follow only links named as arguments
-l count HARD links repeatedly (different thing)
WHAT IS BEING COUNTED
du -sh DIR blocks actually allocated (the default)
du -sh --apparent-size DIR bytes the files contain
big gap between them = many small files, or a sparse file
-l count hard links repeatedly (default: once)
--inodes count FILES instead of bytes
-h / --si / -B M / -k 1024 / 1000 / chosen unit / POSIX-safe
WHEN THE DISK IS FULL
df -h which filesystem, how full
df -i or has it run out of inodes?
du -shx /* | sort -h -r top level, then descend
sudo lsof -nP +L1 deleted-but-open files that du CANNOT see
sudo tune2fs -m 1 /dev/X reclaim the root reserve (data volumes only)
IN SCRIPTS
du -sk DIR | cut -f1 fixed unit, tab-separated, space-safe
find ... -print0 | du -sh --files0-from=- -c
2>/dev/null hides permission errors AND quietly lowers the total
PORTABILITY (BusyBox is what you get in a minimal image)
-s -h -a -c -x -k -m -l -L -H -d N GNU and BusyBox both
--max-depth --apparent-size --exclude GNU only (-d and -b are the portable
--inodes -t -S -B --si spellings of the first two)
UNITS: four env vars change a bare du number
DU_BLOCK_SIZE BLOCK_SIZE BLOCKSIZE POSIXLY_CORRECT
always pass -h, -k or -B in a script
df asks the filesystem. du asks the files. The gap is what du cannot see.
On Btrfs/ZFS add snapshots and shared extents, which no path-walker can see.
De volgende keer dat een schijf volloopt is de hele klus vier commando's: df -h om te bevestigen welk bestandssysteem, df -i om inodes uit te sluiten, en dan twee keer du -h -d 1 -x. En blijft een server vollopen zonder dat iemand kan zeggen waardoor, dan is het antwoord vaak een bestand dat geen naam meer heeft, en dat is precies het ding dat du je nooit heeft kunnen tonen.


Peter is Joomla specialist en Linux admin voor snelle, veilige en schaalbare websites.












