Linux commando: diff
Twee bestanden die hetzelfde zouden moeten zijn, zijn dat niet. Een configuratie die gisteren werkte doet het vandaag niet, aan een teruggezette backup ontbreekt misschien iets, of een leverancier stuurde je een "kleine correctie" voor een bestand dat je zelf al aangepast had. Het commando dat alle drie de vragen beantwoordt is diff, en de uitvoer ervan is geen rapport om te lezen. Het is een reeks instructies die een ander programma kan uitvoeren.
1. De basis
diff vergelijkt twee bestanden regel voor regel en beschrijft wat je in het eerste zou moeten veranderen om er het tweede van te maken. Die richting doet ertoe: een diff is geen neutrale lijst verschillen, het is een recept met een van en een naar.
1.1 De eenvoudigst mogelijke toepassing
Twee versies van een klein configuratiebestand:
$ diff old.php new.php
3c3
< 'host' => 'localhost',
---
> 'host' => 'db.internal',
5c5
< 'debug' => false,
---
> 'debug' => true,
Dit is het oorspronkelijke uitvoerformaat uit 1974, en het leest als een reeks bewerkingen:
| Onderdeel | Betekent |
|---|---|
3c3 |
Regel 3 van het eerste bestand verandert (change) in regel 3 van het tweede |
< |
Een regel uit het eerste bestand |
> |
Een regel uit het tweede bestand |
--- |
De scheiding tussen beide kanten |
Twee andere letters verschijnen in plaats van c: a voor regels die toegevoegd worden (add) en d voor regels die verdwijnen (delete). 7a8 betekent dus "voeg na regel 7 van het oude bestand toe wat regel 8 van het nieuwe wordt".
Je zult dit formaat zelden bewust gebruiken. Hoofdstuk 4 behandelt de twee die je echt wilt. Het is het herkennen waard, want dit krijg je als je vergeet om iets anders te vragen.
1.2 De afsluitcode is het punt
De uitvoer is voor mensen. De afsluitcode is wat diff bruikbaar maakt in een script, en die heeft drie waarden:
$ diff old.php old.php >/dev/null ; echo $?
0 # the files are identical
$ diff old.php new.php >/dev/null ; echo $?
1 # they differ
$ diff old.php nosuch.php >/dev/null 2>&1 ; echo $?
2 # something went wrong
Die 2 is degene die mensen vergeten. Een ontbrekend bestand, een onleesbare map of een verkeerde optie levert allemaal die code op, en een script dat "niet nul" leest als "de bestanden verschillen" meldt een verschil dat in werkelijkheid een typefout in een pad was.
if diff -q "$a" "$b" >/dev/null; then
echo "identical"
elif [ $? -eq 1 ]; then
echo "they differ"
else
echo "diff itself failed"
fi
Naar bovenHet juiste mentale model:
diffvertelt je niet dat twee bestanden verschillen. Hij schrijft de instructies op om van het eerste het tweede te maken, in een formaat dat precies genoeg is datpatchze zonder jou kan uitvoeren.
2. Waar komt de naam vandaan?
diff is kort voor difference, verschil, en ongebruikelijk voor een Unix-commando is dat het hele verhaal. De interessante namen zijn die eromheen:
diff the DIFFerence between two files
patch applies a diff to a file; named for what it does
hunk one contiguous group of changed lines, with its surrounding context
context the unchanged lines printed around a change, so patch can find the place
fuzz how much of that context patch is allowed to ignore (section 7.2)
.rej a rejected hunk, written to a file for you to deal with by hand
.orig the file as it was before patch touched it
Het woord hunk is degene om je eigen te maken, want elk gereedschap in deze familie rapporteert in hunks: diff produceert ze, patch past ze een voor een toe of weigert ze, en git laat je ze los klaarzetten.
Een hunk is een wijziging plus zijn omgeving. Die omgeving is de hele truc: door een paar ongewijzigde regels erboven en eronder mee te sturen, kan de patch nog steeds toegepast worden nadat het bestand verschoven is, want patch zoekt naar de context in plaats van de regelnummers te vertrouwen.
3. Een korte geschiedenis
diff kwam halverwege de jaren zeventig uit Bell Labs, en het probleem dat het oploste was geen versiebeheer. Het was dat computers traag waren en schijven klein, en dat het verschil tussen twee versies van een bestand bewaren veel goedkoper was dan allebei bewaren.
Het deel van het verhaal dat veranderde hoe software geschreven wordt kwam later. In 1985 bracht Larry Wall patch uit, en de combinatie van beide maakte van een diff een verzendmethode in plaats van een beschrijving. Software werd verspreid door diffs naar Usenet-nieuwsgroepen te sturen, waar iedereen ze op zijn eigen kopie van de broncode kon toepassen.
| Periode | Mijlpaal |
|---|---|
| Halverwege jaren 70 | diff verschijnt in Unix bij Bell Labs, met het algoritme gepubliceerd door Hunt en McIlroy |
| 1985 | Larry Wall brengt patch uit, en code gaat per post en nieuwsgroep reizen |
| Rond 1990 | Het unified-formaat verschijnt, compact genoeg om te lezen en in een bericht te citeren |
| Vanaf 2005 | Git en tijdgenoten bouwen op hetzelfde formaat voort in plaats van het te vervangen |
| Vandaag | Elke code review die je ooit gezien hebt is een unified diff met een webpagina eromheen |
De man-pagina van patch vermeldt de betrokkenen nog steeds, en een naam daarin verklaart het formaat dat je dagelijks leest:
$ man patch
AUTHORS
Larry Wall wrote the original version of patch. Paul Eggert removed
patch's arbitrary limits; added support for binary files, setting
file times, and deleting files; ... Other contributors include
Wayne Davison, who added unidiff support, and David MacKenzie, who
added configuration and backup support.
"Unidiff support" is het unified-formaat. Het is het waarderen waard hoe volledig het gewonnen heeft: GitHub, GitLab, elk gereedschap voor code review en git diff zelf tonen allemaal het formaat dat aan een programma uit 1985 werd toegevoegd zodat patches in een e-mail zouden passen.
Naar bovenLeren een unified diff te lezen is niet een Unix-commando leren. Het is het formaat leren waarin elke code review ter wereld geschreven is.
4. Eenvoudige toepassingen
4.1 Het formaat dat je echt wilt: -u
De vlag -u (kort voor unified) toont een blok tekst met de verwijderingen en toevoegingen door elkaar, in plaats van twee losse lijsten:
$ diff -u old.php new.php
--- old.php 2026-08-23 17:15:36.275783415 +0200
+++ new.php 2026-08-23 17:15:36.276783411 +0200
@@ -1,7 +1,7 @@
<?php
$config = [
- 'host' => 'localhost',
+ 'host' => 'db.internal',
'user' => 'joomla',
- 'debug' => false,
+ 'debug' => true,
];
echo "ready\n";
Lees het als een bestand met twee versies over elkaar heen:
| Regel begint met | Betekent |
|---|---|
--- |
Het oorspronkelijke bestand, met zijn tijdstempel |
+++ |
Het nieuwe bestand |
@@ |
Het begin van een hunk (uitgelegd in 4.2) |
| Een spatie | Context: ongewijzigd, in beide aanwezig |
- |
Weggehaald uit het origineel |
+ |
Toegevoegd in de nieuwe versie |
Een gewijzigde regel verschijnt altijd twee keer, een keer als - en een keer als +. Er is geen markering voor "gewijzigd", want op dit niveau bestaat een regel wijzigen niet: je verwijdert de oude en voegt een nieuwe toe.
Er bestaat een ouder formaat -c (context) dat de twee versies in aparte blokken toont met ! voor wijzigingen. Je komt het tegen in oude documentatie. Unified zegt hetzelfde in ongeveer de helft van de ruimte, en daarom nam het het over.
4.2 De @@-regel lezen
Dit is het deel dat iedereen overslaat, en het kost dertig seconden om te leren. Hier is een bestand met drie losse wijzigingen:
$ diff -u big-old.txt big-new.txt
@@ -1,6 +1,6 @@
line 1
line 2
-line 3
+line 3 CHANGED
line 4
line 5
line 6
@@ -8,6 +8,7 @@
line 8
line 9
line 10
+line 10.5 INSERTED
line 11
line 12
line 13
@@ -22,7 +23,7 @@
line 22
line 23
line 24
-line 25
+line 25 CHANGED
line 26
Elke @@-regel zegt waar de hunk in beide bestanden zit:
@@ -22,7 +23,7 @@
| | | |
| | | └─ ... and covers 7 lines there
| | └─── in the NEW file it starts at line 23 ...
| └───── ... and covers 7 lines
└─────── in the OLD file this hunk starts at line 22 ...
Kijk nu naar de drie hunks samen. De eerste begint in beide op regel 1. De tweede begint in beide op regel 8, maar beslaat 6 regels in het oude bestand en 7 in het nieuwe, omdat er een regel is ingevoegd. Bij de derde hunk zijn de bestanden uit elkaar gelopen: hij begint op regel 22 in het oude bestand en op regel 23 in het nieuwe. Dat ene getal is het lopende saldo van alles wat erboven is toegevoegd en weggehaald.
Je kunt instellen hoeveel context er meekomt, en dat verandert hoe de hunks gegroepeerd worden:
$ diff -U1 big-old.txt big-new.txt | grep -c '^@@'
3
$ diff -U5 big-old.txt big-new.txt | grep -c '^@@'
2
Met vijf regels context liggen twee van de wijzigingen dicht genoeg bij elkaar dat hun omgevingen overlappen, dus voegt diff ze samen tot een hunk. De standaard is drie, en dat is een goed compromis en de reden dat vrijwel elke patch die je ziet precies drie ongewijzigde regels rond elke wijziging heeft.
4.3 Naast elkaar
Om te lezen in plaats van om te patchen zet -y de twee bestanden in kolommen:
$ diff -y --width=68 old.php new.php
<?php <?php
$config = [ $config = [
'host' => 'localhost', | 'host' => 'db.internal',
'user' => 'joomla', 'user' => 'joomla',
'debug' => false, | 'debug' => true,
]; ];
echo "ready\n"; echo "ready\n";
De markering in de middelste kolom vertelt je wat er gebeurde: | gewijzigd, < alleen in het linkerbestand, > alleen in het rechter. Voeg bij een lang bestand --suppress-common-lines toe zodat je alleen de wijzigingen ziet:
$ diff -y --suppress-common-lines --width=60 old.php new.php
'host' => 'localhost', | 'host' => 'db.internal'
'debug' => false, | 'debug' => true,
4.4 Kleur
GNU diff kan zijn eigen uitvoer kleuren, en dat maakt een unified diff veel makkelijker te scannen:
$ diff --color=auto -u old.php new.php
auto is de waarde die je wilt: kleur als de uitvoer een terminal is, en platte tekst als hij doorgesluisd of omgeleid wordt. Dat doet ertoe, want een patchbestand met kleurcodes erin is geen patchbestand meer. --color=always bestaat om naar een pager te sturen, en is overal elders de verkeerde keuze.
Een alias maakt het blijvend:
alias diff='diff --color=auto -u'
Naar boven5. Gemiddelde toepassingen
5.1 Twee mappen vergelijken
Hier houdt diff op gereedschap voor programmeurs te zijn en wordt het gereedschap voor beheerders. -r (kort voor recursive) loopt twee bomen af:
$ diff -r v1 v2
Only in v2: added.txt
diff -r v1/changed.txt v2/changed.txt
1c1
< old
---
> new
diff -r v1/inc/deep.txt v2/inc/deep.txt
1c1
< x
---
> y
Only in v1: removed.txt
Op twee echte websites is die uitvoer duizenden regels lang. Wat je vrijwel altijd wilt is er -q bij (kort voor brief), dat meldt welke bestanden verschillen zonder de inhoud te tonen:
$ diff -rq v1 v2
Only in v2: added.txt
Files v1/changed.txt and v2/changed.txt differ
Files v1/inc/deep.txt and v2/inc/deep.txt differ
Only in v1: removed.txt
diff -rq is het nuttigste commando uit dit artikel voor iedereen die servers beheert. Het beantwoordt "wat is er verschillend tussen deze twee kopieën van de site?" in een enkele regel, en het is het eerste om te draaien als een uitrol misgegaan is, als je vermoedt dat een kernbestand aangepast is, of als je wilt weten wat een update werkelijk veranderd heeft.
Een optie verandert de betekenis. -N behandelt een ontbrekend bestand als een leeg bestand, dus "alleen in" wordt "verschilt":
$ diff -rqN v1 v2
Files v1/added.txt and v2/added.txt differ
Files v1/changed.txt and v2/changed.txt differ
Files v1/inc/deep.txt and v2/inc/deep.txt differ
Files v1/removed.txt and v2/removed.txt differ
Gebruik -N als je een patch maakt, want een patch moet bestanden kunnen aanmaken en verwijderen. Laat hem weg als je de uitvoer zelf leest, want "alleen in" is informatiever dan "verschilt".
Voordat je hier iets van vertrouwt: lees paragraaf 7.1. diff -rq vergelijkt de inhoud van bestanden en verder niets, en wat het weglaat doet er meer toe dan de meeste mensen verwachten.
5.2 Bestanden en mappen uitsluiten
Op een echte site verzuipt het vorige commando in ruis. Caches, logs en gegenereerde miniaturen verschillen voortdurend en zeggen je niets:
$ diff -rq site-a site-b
Files site-a/cache/x.tmp and site-b/cache/x.tmp differ
Files site-a/index.php and site-b/index.php differ
Files site-a/logs/error.log and site-b/logs/error.log differ
Alleen de middelste regel doet ertoe. -x (kort voor exclude) neemt een patroon in shell-stijl en mag herhaald worden:
$ diff -rq -x cache -x '*.log' site-a site-b
Files site-a/index.php and site-b/index.php differ
Zodra de lijst groeit, zet je hem in een bestand en geef je dat mee met -X. Dat bestand hoort in versiebeheer, naast het script dat de vergelijking draait:
$ cat skip.txt
cache
logs
tmp
*.log
$ diff -rq -X skip.txt site-a site-b
Files site-a/index.php and site-b/index.php differ
Een regel pakt iedereen een keer: het patroon wordt vergeleken met de bestandsnaam, niet met het pad. Een pad met een schuine streep erin komt stilzwijgend met niets overeen:
$ diff -rq -x 'logs/*' site-a site-b
Files site-a/cache/x.tmp and site-b/cache/x.tmp differ
Files site-a/index.php and site-b/index.php differ
Files site-a/logs/error.log and site-b/logs/error.log differ <-- not excluded
Sluit logs uit, niet logs/* en niet site-a/logs. De kale naam is waar diff elke regel mee vergelijkt terwijl hij de boom afloopt, en een map op naam uitsluiten slaat alles erin over.
5.3 Negeren wat je niet interesseert
Standaard telt elke byte mee, ook witruimte:
$ diff ws1.txt ws2.txt
1,2c1,2
< hello world
< second line
---
> hello world
> second line
De ene regel kreeg er een spatie in het midden bij, de andere spaties aan het eind, en allebei worden ze gemeld. Vier vlaggen zetten dat uit, van mildst naar breedst:
| Vlag | Negeert |
|---|---|
-b |
Verschillen in de hoeveelheid witruimte |
-w |
Alle witruimte, overal |
-B |
Regels die volledig leeg zijn |
-i |
Hoofdletters en kleine letters |
Er is ook -I, dat een reguliere expressie neemt en elke wijziging negeert op een regel die eraan voldoet. Zo vergelijk je twee gegenereerde rapporten zonder dat het tijdstempel bovenaan elk bestand anders laat lijken:
$ diff r1.txt r2.txt
1c1
< Generated: 2026-08-23
---
> Generated: 2026-08-24
$ echo $?
1
$ diff -I '^Generated:' r1.txt r2.txt
$ echo $?
0
Gebruik deze om te onderzoeken, niet om te beslissen. Een witruimtewijziging is onzichtbaar voor -w en zeer zichtbaar voor Python, YAML, en alles wat een here-document leest.
5.4 De valkuil van regeleindes
Af en toe toont een bestand dat "niemand aangeraakt heeft" elke regel als gewijzigd. Dit zijn vrijwel altijd de regeleindes, en het is het meest voorkomende loze alarm in webwerk, want een bestand dat op Windows bewerkt en via FTP geüpload is komt terug met een carriage return op elke regel.
$ diff crlf.txt lf.txt | cat -A
1,2c1,2$
< line one^M$
< line two^M$
---$
> line one$
> line two$
De ^M aan het eind van elke regel is de carriage return, zichtbaar gemaakt door cat -A. Zonder die truc zien beide kanten er identiek uit en lijkt de diff onzinnig. Twee commando's bevestigen het en een vlag negeert het:
$ file crlf.txt lf.txt
crlf.txt: ASCII text, with CRLF line terminators
lf.txt: ASCII text
$ diff --strip-trailing-cr crlf.txt lf.txt ; echo $?
0
Het bestand echt repareren is beter dan het verschil voor altijd negeren. dos2unix doet dat, en sed -i 's/\r$//' bestand ook op een machine die dat programma niet heeft.
5.5 Als het geen tekst is
diff geeft het op bij binaire bestanden, en zegt dat ook:
$ diff bin1.dat bin2.dat
Binary files bin1.dat and bin2.dat differ
Dat is meestal alles wat je nodig hebt, en de afsluitcode werkt gewoon. Wil je details, dan is cmp het juiste gereedschap: dat vergelijkt byte voor byte en vertelt je waar het eerste verschil zit.
$ cmp bin1.dat bin2.dat
bin1.dat bin2.dat differ: byte 25, line 1
$ cmp -l bin1.dat bin2.dat | head -3
25 60 300
26 155 72
41 50 30
cmp -l somt elke afwijkende byte op met zijn positie en de twee waarden in octaal. Voor een kale ja-of-nee op grote bestanden zijn cmp -s (stil) en diff -q even snel, want beide stoppen bij het eerste verschil; op twee identieke bestanden van 200 MB kostten ze hier 0,059s en 0,062s. Kies cmp als je wilt weten waar het verschil zit, en diff als je wilt weten wat het zegt.
Als je echt naar de bytes moet kijken, maak er dan eerst tekst van en vergelijk die. xxd maakt een hexdump met een regel per zestien bytes, en dat is precies de vorm die een regelgebaseerd gereedschap wil:
$ diff -u <(xxd h1.bin) <(xxd h2.bin)
@@ -1,4 +1,4 @@
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
-00000010: 0300 3e00 0100 0000 306d 0000 0000 0000 ..>.....0m......
-00000020: 4000 0000 0000 0000 2824 0200 0000 0000 @.......($......
+00000010: 0300 3e00 0100 0000 c03a 0000 0000 0000 ..>......:......
+00000020: 4000 0000 0000 0000 1892 0000 0000 0000 @...............
Dit maakt binaire formaten niet leesbaar, maar het maakt van "deze twee bestanden verschillen ergens" iets als "deze twee bestanden verschillen op positie 0x10 en 0x20", en dat is vaak genoeg om een versienummer, een tijdstempel of een headerveld te herkennen.
5.6 Dingen vergelijken die geen bestanden zijn
Bash laat je de uitvoer van een commando aanbieden aan alles dat een bestandsnaam verwacht, met <(...). Dat haalt de tijdelijke bestanden weg uit een hele categorie controles:
$ diff <(printf 'apache2\nmysql\nphp\n') <(printf 'apache2\nnginx\nphp\n')
2c2
< mysql
---
> nginx
Hetzelfde idee beantwoordt echte vragen over twee servers:
$ diff <(ssh web1 'dpkg -l | sort') <(ssh web2 'dpkg -l | sort')
$ diff <(ssh web1 'php -m | sort') <(ssh web2 'php -m | sort')
$ diff <(cd /var/www/live && find . | sort) <(cd /var/www/staging && find . | sort)
Sorteer beide kanten. diff vergelijkt op volgorde, dus twee identieke pakketlijsten in een andere volgorde leveren pagina's ruis op. Die ene gewoonte maakt deze techniek bruikbaar.
Er is een ruw randje. Omdat de "bestanden" anonieme pipes zijn, noemt de header ze naar bestandsdescriptors, en dat zegt je niets:
$ diff -u <(echo a) <(echo b) | head -2
--- /dev/fd/63 2026-08-23 18:33:16.048005790 +0200
+++ /dev/fd/62 2026-08-23 18:33:16.048005790 +0200
--label vervangt ze, een keer per kant, op volgorde:
$ diff -u --label production --label staging <(echo a) <(echo b) | head -2
--- production
+++ staging
Gebruik het zodra de uitvoer door iemand anders gelezen wordt of in een ticket geplakt. Een diff waarvan de twee kanten production en staging heten heeft geen uitleg nodig; een waarvan de kanten /dev/fd/63 en /dev/fd/62 heten heeft een alinea nodig.
5.7 Normaliseer voordat je vergelijkt
De vorige paragraaf eindigde op een gewoonte die het waard is om als regel op te schrijven: de beste vergelijking is meestal geen vergelijking van de ruwe invoer. Twee kanten kunnen dezelfde informatie in een andere volgorde, een andere opmaak of een andere codering bevatten, en een regelgebaseerd gereedschap meldt dat allemaal als wijziging.
Het patroon is altijd hetzelfde:
raw input --> normalise --> compare
Sorteer, en zet de taalinstelling vast. Beide kanten sorteren is de voor de hand liggende helft. De helft die vrijwel niemand noemt is dat de sorteervolgorde zelf van de taalinstelling afhangt, dus dezelfde gegevens gesorteerd op twee machines met verschillende taalinstellingen komen niet overeen:
$ cat loc.txt
apple
Banana
cherry
$ LC_ALL=en_US.UTF-8 sort loc.txt
apple Banana cherry
$ LC_ALL=C sort loc.txt
Banana apple cherry <-- capitals first, by byte value
Vergelijk die twee volgordes met elkaar en je krijgt verschillen op elke regel, uit identieke invoer. Als de twee kanten van twee verschillende servers komen, is dat precies wat er gebeurt. Zet de taalinstelling aan beide kanten vast en het probleem verdwijnt:
$ diff <(LC_ALL=C sort list1.txt) <(LC_ALL=C sort list2.txt)
$ diff <(ssh web1 'LC_ALL=C dpkg-query -W | sort') \
<(ssh web2 'LC_ALL=C dpkg-query -W | sort')
LC_ALL=C geeft een kale bytevolgorde die overal hetzelfde is. Het is niet de volgorde die een mens zou kiezen, en dat maakt niet uit, want niemand leest het: de twee kanten hoeven het alleen met elkaar eens te zijn.
Gebruik gereedschap dat het formaat kent, als dat bestaat. Opnieuw opgemaakte JSON is voor diff een volledige herschrijving, ook als er niets veranderd is. jq -S drukt het opnieuw af met gesorteerde sleutels, zodat alleen echte verschillen overblijven:
$ diff -u --label old --label new <(jq -S . old.json) <(jq -S . new.json)
Hetzelfde idee dekt de meeste klachten van het type "waarom is alles anders?": maak de XML netjes op, sorteer de databasedump, haal de tijdstempelregel weg met -I, maak de regeleindes gelijk. Doe het normaliseren een keer, in het commando, en de diff wordt leesbaar.
6. Gevorderde toepassingen
6.1 Een patch maken
Een patch is niet meer dan een unified diff die in een bestand is opgeslagen. Twee regels maken het verschil tussen een patch die toegepast kan worden en een die dat niet kan.
Gebruik -u, en neem de mapnaam mee. Draai de vergelijking een niveau boven de boom, zodat de paden in de patch een voorvoegsel hebben dat weggehaald kan worden:
$ diff -u site-a/components/helper.php site-b/components/helper.php > fix.patch
$ cat fix.patch
--- site-a/components/helper.php 2026-08-23 17:16:58.021471057 +0200
+++ site-b/components/helper.php 2026-08-23 17:16:58.022471053 +0200
@@ -1,5 +1,5 @@
<?php
function getLimit()
{
- return 20;
+ return 50;
}
Voor een hele boom voeg je -r en -N toe, zodat nieuwe en verwijderde bestanden meekomen:
$ diff -ruN site-a site-b > release.patch
6.2 Toepassen: de -p-niveaus
Hier lopen de meeste pogingen stuk, en de reden is eenvoudig zodra je hem ziet. De patch bevat een pad. patch moet dat pad omzetten naar een bestand op jouw schijf, en -p vertelt hem hoeveel voorste mapniveaus hij moet weggooien.
Path in the patch: site-a/components/helper.php
-p0 looks for site-a/components/helper.php
-p1 looks for components/helper.php
-p2 looks for helper.php
De juiste waarde hangt dus volledig af van waar jij staat. Vanuit een kopie van de site staat het voorvoegsel site-a/ niet op schijf en moet het weggehaald worden:
$ cd /var/www/site
$ patch -p0 < ../fix.patch
can't find file to patch at input line 3
Perhaps you used the wrong -p or --strip option?
$ patch -p1 < ../fix.patch
patching file components/helper.php
-p1 is meestal het antwoord, want patches worden per afspraak een niveau boven de boom gemaakt, en het is wat git maakt en verwacht. Bij twijfel: lees de ----regel van de patch en tel.
6.3 Proefdraaien, backups en ongedaan maken
Drie gewoontes maken van patch iets dat je in de hand hebt in plaats van iets waarvan je hoopt dat het werkt.
Probeer het eerst. --dry-run doet alles behalve schrijven:
$ patch -p1 --dry-run < ../fix.patch
checking file components/helper.php
$ echo $?
0
Let op de formulering: "checking file" bij een proefrun, "patching file" als het echt is. Zo zie je in een log welke van de twee gebeurd is.
Bewaar het origineel. Standaard bewaart patch niets. -b zet de vorige versie naast het bestand:
$ patch -p1 -b < ../fix.patch
$ ls -A components/
helper.php helper.php.orig
Weet hoe je het terugdraait. Een patch is omkeerbaar, want hij beschrijft beide kanten van elke wijziging. -R draait hem achterstevoren:
$ patch -p1 -R < ../fix.patch
patching file components/helper.php
Dat is het echte antwoord op "de correctie maakte het erger". Je hebt het oude bestand niet nodig; je hebt de patch nodig die het nieuwe opleverde.
6.4 Als het niet schoon toegepast wordt
Echte bestanden schuiven op. patch is daarvoor gebouwd, en hij vertelt je precies hoe hard hij heeft moeten werken.
Offset betekent dat hij de context ergens anders in het bestand vond. Hier waren er twee commentaarregels bovenaan toegevoegd:
$ patch -p1 < ../fix.patch
patching file components/helper.php
Hunk #1 succeeded at 3 with fuzz 1 (offset 2 lines).
$ echo $?
0
Een offset is normaal en ongevaarlijk. patch zocht naar buiten toe vanaf het regelnummer in de hunk-header, vond de omringende regels twee regels lager, en paste de wijziging daar toe. Precies hiervoor bestaan de contextregels.
Weigering betekent dat hij het opgaf. Dezelfde patch twee keer toepassen is de gebruikelijke oorzaak:
$ patch -p1 < ../fix.patch
patching file components/helper.php
Reversed (or previously applied) patch detected! Assume -R? [n]
Apply anyway? [n]
Skipping patch.
1 out of 1 hunk ignored -- saving rejects to file components/helper.php.rej
$ echo $?
1
Twee dingen om op te merken. patch herkende dat het bestand het resultaat al bevatte en bood aan het om te keren, en dat is een oprecht knap stuk techniek uit 1985. En hij schreef een .rej-bestand met de hunks die hij niet kon plaatsen:
$ cat components/helper.php.rej
--- components/helper.php
+++ components/helper.php
@@ -1,5 +1,5 @@
<?php
function getLimit()
{
- return 20;
+ return 50;
}
Een .rej-bestand is geen foutmelding. Het is het werk dat voor jou overblijft. Zoek na elke patch die weigeringen meldt de boom af voordat je de klus afgerond noemt:
$ find . -name '*.rej' -o -name '*.orig'
Tussen "schoon toegepast" en "geweigerd" zit een derde uitkomst die een eigen paragraaf verdient, want hij slaagt en hij zou je zorgen moeten baren. Paragraaf 7.2.
6.5 Driewegs, en waar de conflictmarkeringen van git vandaan komen
diff3 vergelijkt drie bestanden: dat van jou, dat van hen, en de gemeenschappelijke voorouder waar beide van uitgingen. Dat is de vorm van elke samenvoeging.
$ diff3 -m mine.txt base.txt theirs.txt
a
<<<<<<< mine.txt
MINE
||||||| base.txt
BASE
=======
THEIRS
>>>>>>> theirs.txt
c
Iedereen die git gebruikt heeft herkent die uitvoer meteen. Die markeringen zijn geen uitvinding van git: git gebruikt dezelfde driewegslogica en drukt hetzelfde formaat af. Het middelste deel, tussen ||||||| en =======, is hoe de regel eruitzag voordat een van beide kanten hem aanraakte, en dat is het stuk waarvan mensen het vaakst wensen dat ze het hadden bij het oplossen van een conflict.
De volgorde van de argumenten is het onthouden waard, want hij is niet alfabetisch en niet vanzelfsprekend: mine, base, theirs. De gemeenschappelijke voorouder staat in het midden.
6.6 Een patch toepassen die je niet zelf schreef
patch schrijft bestanden, dus een patch van een onbekende bron is onvertrouwde invoer. De traditionele zorg is dat een patch een pad zou kunnen bevatten dat buiten je boom wijst. Op GNU patch 2.7.6 is dat afgevangen, en het is de moeite waard om de weigeringen te zien in plaats van het aan te nemen.
Een patch die een absoluut pad noemt wordt met naam en toenaam geweigerd:
$ patch -p0 < ../abs.patch
Ignoring potentially dangerous file name /home/peter/sec/outside.txt
can't find file to patch at input line 3
$ cat ../outside.txt
ORIGINAL <-- untouched
Een patch die met ../ naar buiten klimt wordt ook geweigerd, zowel bij -p0 als bij -p1:
$ patch -p1 < ../evil.patch
can't find file to patch at input line 3
Perhaps you used the wrong -p or --strip option?
$ cat ../outside.txt
ORIGINAL <-- still untouched
De ontsnappingsroutes zijn dus dicht. Wat niet dicht is, is alles wat de patch binnen je boom gewoon mag doen, en dat is het deel dat er werkelijk toe doet:
- Lees hem eerst. Een patch is platte tekst.
less fix.patchtoont je elke regel die hij gaat wijzigen, en dat is meer dan je van de meeste dingen die je installeert kunt zeggen. - Kijk welke bestanden hij aanraakt voordat je naar de inhoud kijkt:
grep '^+++' fix.patch. - Eerst proefdraaien, dan toepassen.
patch -p1 --dry-runkost niets. - Werk op een kopie als de boom productie is, en vergelijk de kopie daarna terug.
Die laatste is de algemene regel waar dit hele artikel steeds op uitkomt: het gereedschap dat een wijziging toepast en het gereedschap dat hem controleert zijn hetzelfde gereedschap, in tegengestelde richting gebruikt.
Naar boven7. Iets wat de meeste gebruikers niet weten
7.1 diff -rq vergelijkt inhoud en verder niets
Paragraaf 5.1 noemde diff -rq het nuttigste commando uit dit artikel. Dat is het ook, en het zal je ook vertellen dat twee mappen overeenkomen terwijl ze wezenlijk verschillen, want het vergelijkt de inhoud van bestanden en verder niets.
Hier zijn twee mappen. Een bestand heeft in beide dezelfde inhoud, maar de permissies en het tijdstempel verschillen:
$ ls -l p1/f.txt p2/f.txt
-rw-r--r-- Jan 1 2020 p1/f.txt
-rwxrwxrwx Aug 23 17:16 p2/f.txt
$ diff -rq p1 p2
$ echo $?
0 <-- "identical"
Een bestand ging van 644 naar 777, schrijfbaar voor iedereen, en diff meldde helemaal niets. Op een website is dat het verschil tussen een dichtgezet bestand en een dat iedereen kan herschrijven, en een restore die zo gecontroleerd is komt met de verkeerde permissies door de test.
Bij symbolische links is het erger, want diff volgt ze. Hier is s1/ptr een symlink en s2/ptr een echt bestand met dezelfde inhoud:
$ ls -l s1/ptr s2/ptr
lrwxrwxrwx s1/ptr -> real.txt
-rw-rw-r-- s2/ptr
$ diff -rq s1 s2
$ echo $?
0 <-- still "identical"
De structuur was vernield en de controle kwam erdoor. Voor elke site met de standaardindeling current -> releases/2026-08 bij het uitrollen is dat precies de storing die ertoe zou doen, en precies degene die dit commando verbergt.
Moderne GNU diff heeft er een vlag voor:
$ diff -rq --no-dereference s1 s2
File s1/ptr is a symbolic link while file s2/ptr is a regular file
$ echo $?
1
De eerlijke samenvatting van wat diff -rq controleert is dus kort:
| Eigenschap | Gecontroleerd door diff -rq? |
|---|---|
| Inhoud van bestanden | Ja. Dat is de hele taak. |
| Bestanden aanwezig of afwezig | Ja, gemeld als "Only in" |
| Permissies | Nee |
| Eigenaar en groep | Nee |
| Tijdstempels | Nee |
| Symlink tegenover echt bestand | Nee, tenzij je --no-dereference toevoegt |
| Lege mappen | Gemeld als "Only in", maar nooit als een verschil in inhoud |
Paragraaf 7.5 geeft het commando dat de rest afdekt.
7.2 Fuzz past je patch toe op code die verder is gegaan
Paragraaf 6.4 liet een patch luid falen zien. Dit is de storing die dat niet doet.
patch vindt een hunk door zijn contextregels te matchen. Als hij ze niet allemaal kan matchen, stopt hij niet: hij probeert het opnieuw terwijl hij de buitenste contextregels negeert. Het naslagwerk noemt de grens onomwonden:
$ man patch
First patch looks for a place where all lines of the context match.
If no such place is found, and it's a context diff, and the maximum
fuzz factor is set to 1 or more, then another scan takes place
ignoring the first and last line of context. If that fails, and the
maximum fuzz factor is set to 2 or more, the first two and last two
lines of context are ignored, and another scan is made.
(The default maximum fuzz factor is 2.)
Kijk wat dat toelaat. Hier is de functie waartegen de patch geschreven was door iemand anders hernoemd, dus de context komt niet meer overeen:
$ patch -p1 < ../fix.patch
patching file components/helper.php
Hunk #1 succeeded at 1 with fuzz 2.
$ echo $?
0
Afsluitcode 0. Een script ziet succes. Maar patch heeft zojuist een wijziging geschreven in een bestand waarvan de omringende code niet de code is waar de auteur van de patch naar keek, want hij gooide aan beide kanten twee regels context weg om een match te vinden.
Soms is dat precies goed en bespaart het je een middag. Soms zet het een correctie in een functie die niet langer de bedoelde functie is. Het punt is dat het je niet gevraagd werd.
patchdie slaagt is niet hetzelfde alspatchdie gelijk heeft. Elke regel waarin fuzz voorkomt betekent dat het bestand opgeschoven was en dat patch gegokt heeft; lees wat hij gedaan heeft voordat je verdergaat.
Zet het gokken uit voor alles wat onbeheerd draait. -F0 zet de maximale fuzz op nul, zodat een hunk of precies past of faalt:
$ patch -p1 -F0 < ../fix.patch
patching file components/helper.php
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED -- saving rejects to file components/helper.php.rej
$ echo $?
1
Dat is het gedrag dat je in een uitrolscript wilt: een duidelijke storing waar je op kunt handelen, in plaats van een stil succes dat je later moet ontdekken.
7.3 diff ziet geen verplaatst blok
diff vergelijkt niet regel 1 met regel 1 en regel 2 met regel 2. Als dat zo was, zou een regel bovenaan invoegen elke regel eronder als gewijzigd melden. In plaats daarvan zoekt hij de langste reeks regels die de twee bestanden gemeen hebben en behandelt hij de rest als toegevoegd of verwijderd, en daarom meldt het invoegen van een regel precies een toegevoegde regel.
Die uitlijning maakt diff bruikbaar, en ze heeft een blinde vlek: het begrip verplaatsing bestaat niet. Neem een bestand en verwissel de volgorde van twee functies, zonder er iets in te veranderen:
$ diff -u mv1.txt mv2.txt
@@ -1,6 +1,6 @@
header
-function A
-body A
function B
body B
+function A
+body A
footer
Er is niets toegevoegd en niets weggehaald, maar de diff toont twee verwijderingen en twee toevoegingen. Voor het gereedschap is er geen verschil tussen "dit blok is verplaatst" en "dit blok is hier weggehaald en daar is een identiek blok geschreven".
Dat doet ertoe bij review. Een patch die een groot bestand herordent ziet er enorm uit en leest alsof alles herschreven is, en de echt nieuwe regels liggen begraven tussen de verplaatste. Drie uitwegen, op volgorde van hoeveel je moet installeren:
- Meng verplaatsingen niet met bewerkingen. Ga je een bestand herordenen, doe dat dan in een wijziging die alleen herordent, zodat de volgende diff leesbaar is.
- Vraag om een andere uitlijning.
-d(--minimal) vraagtdiffom extra moeite te doen om een kleinere set wijzigingen te vinden. Het is trager, en op eenvoudige bestanden geeft het meestal hetzelfde antwoord, maar bij een slecht uitgelijnde diff is het een poging waard. - Gebruik gereedschap dat verplaatsingen volgt bij het reviewen, zoals
git diff --color-moved, dat verplaatste blokken in een andere kleur zet in plaats van te doen alsof ze nieuw zijn.
Het bredere punt is dat een diff een geldige beschrijving is van hoe je van A naar B komt, niet de enige en niet per se degene die een mens geschreven zou hebben. Twee gereedschappen kunnen voor hetzelfde paar bestanden verschillend ogende uitvoer geven en allebei gelijk hebben.
7.4 Een diff is een programma, geen rapport
Al het andere in dit artikel volgt uit een idee dat makkelijk te missen is: de uitvoer van diff is met opzet machineleesbaar, en de machine die hem leest is patch.
Daarom is het formaat zo pietluttig. De spatie vooraan een contextregel is geen versiering, het is de markering voor "ongewijzigd", en hem weghalen breekt de patch. De regeltellingen in de @@-header moeten overeenkomen met het aantal regels dat volgt. Een editor die spaties aan het eind weghaalt kan een patchbestand ongeldig maken. Een mailprogramma dat lange regels afbreekt ook, en daarom werden patches van oudsher als bijlage verstuurd en heeft elk project een pagina over hoe je ze niet verminkt.
Het verklaart ook de twee regels die mensen verrassen:
- Bewerk nooit een patchbestand om het te "repareren", tenzij je bereid bent de regeltellingen in elke hunk-header die je aanraakt mee te corrigeren.
- Laat nooit kleur in een patch komen.
--color=always > fix.patchlevert een bestand op dat er perfect uitziet en datpatchniet kan lezen, want elke regel begint nu met een escape-reeks in plaats van met een spatie, een plus of een min.
7.5 Een teruggezette backup goed controleren
Zet 7.1 naast de rest en je krijgt de controle die na een restore of een verhuizing werkelijk de moeite waard is. Hij bestaat uit twee helften, want geen enkel commando dekt allebei.
Eerst de inhoud:
$ diff -rq --no-dereference /var/www/live /mnt/restore/live
Daarna alles wat diff negeert, door de metagegevens in tekst om te zetten en die te vergelijken:
$ diff \
<(cd /var/www/live && find . -printf '%p %m %u:%g\n' | sort) \
<(cd /mnt/restore/live && find . -printf '%p %m %u:%g\n' | sort)
3c3
< ./f.txt 644 www-data:www-data
---
> ./f.txt 777 www-data:www-data
De opmaakreeks van find -printf doet het werk: %p het pad, %m de permissiebits in octaal, %u:%g de eigenaar en de groep. Beide kanten sorteren maakt de vergelijking stabiel. Het resultaat is een regel per bestand waarvan de modus of het eigendom veranderde, en dat is precies wat de inhoudsvergelijking niet kan zien.
Ditzelfde paar commando's beantwoordt een tweede vraag die vaker opkomt dan iemand zou willen: is er iets op deze site gewijzigd dat niet gewijzigd had mogen worden? Download een schone kopie van dezelfde versie en vergelijk die met de draaiende site. Elk bestand dat verschilt is of je eigen aanpassing, of iets waar je heel goed naar moet kijken.
Trek het nog een stap breder en je hebt de nuttigste gewoonte uit dit artikel. Leg de toestand als tekst vast voordat je iets verandert, leg hem daarna opnieuw vast, en vergelijk de twee. Het kost twee commando's en het beantwoordt precies "wat heeft dat nu eigenlijk gedaan?":
$ before=$(mktemp) after=$(mktemp)
$ trap 'rm -f "$before" "$after"' EXIT
$ dpkg-query -W | LC_ALL=C sort > "$before"
$ sudo apt upgrade
$ dpkg-query -W | LC_ALL=C sort > "$after"
$ diff -u --label before --label after "$before" "$after"
De trap-regel maakt het veilig om in een script te zetten: de tijdelijke bestanden worden ook opgeruimd als het vroegtijdig stopt. Alles wat tekst afdrukt werkt als momentopname, en op Linux is dat vrijwel alles:
find /etc -type f | LC_ALL=C sort which configuration files exist
systemctl list-unit-files --state=enabled which services will start
ss -tulpn which ports are listening
php -m | LC_ALL=C sort which PHP extensions are loaded
crontab -l ; ls /etc/cron.d what is scheduled
Draai een van die commando's voor een verhuizing en daarna nog eens, en de verschillen tussen de twee servers zijn geen kwestie van geheugen meer.
7.6 Weten waar diff ophoudt
Een deel van vakmanschap is weten wanneer een gereedschap het verkeerde is.
| Nodig | Gebruik | Waarom |
|---|---|---|
| Gestructureerde gegevens vergelijken | jq -S en dan diff, of gereedschap dat het formaat kent |
Opnieuw opgemaakte JSON is voor een regelgebaseerd gereedschap een volledige herschrijving |
| Woord voor woord vergelijken | git diff --word-diff, wdiff |
diff kent geen eenheid kleiner dan een regel |
| Wijzigingen door de tijd heen volgen | git |
diff vergelijkt twee toestanden; hij onthoudt niets |
| Een database vergelijken | Een dump, gesorteerd, en dan diff | De volgorde van rijen ligt niet vast, dus zonder sorteren is de diff betekenisloos |
| Synchroniseren in plaats van rapporteren | rsync -n |
Een proefrun van rsync toont wat er zou veranderen, en kan het daarna doen |
| Twee gesorteerde lijsten vergelijken | comm |
Geeft je drie kolommen: alleen-in-A, alleen-in-B, en beide |
Die laatste rij wordt te weinig gebruikt. Heb je twee lijsten met namen, dan beantwoorden comm -13 en comm -23 de vraag "wat staat alleen aan deze kant?" directer dan een diff lezen.
8. Best practices
- Neem
diff -uals standaard. Het is compact, het is wat elk gereedschap voor code review je toont, en het is het enige formaat datpatchengitallebei zonder discussie aannemen. Maak er desnoods een alias van. - Leer de
@@-regel een keer. Oude begin en lengte, nieuwe begin en lengte. Dertig seconden lezen maakt van elke patch en elke pull request een zin in plaats van een muur symbolen. - Gebruik
diff -rqals eerste zet bij elke "wat is er veranderd?"-vraag, en voeg--no-dereferencetoe zodat symlinks als symlinks vergeleken worden. - Sluit de ruis uit met
-xof-X, en onthoud dat het patroon met namen vergeleken wordt en niet met paden. - Controleer de metagegevens apart. Inhoud is maar de helft van een restore. De vergelijking met
find -printfuit paragraaf 7.5 vangt de wijzigingen in permissies en eigendom op waardiffblind voor is. - Sorteer beide kanten, en zet de taalinstelling vast. Pakketlijsten en mapoverzichten komen in willekeurige volgorde terug, en de volgorde zelf hangt af van
LANG.LC_ALL=C sortaan beide kanten. - Test op afsluitcode 2, niet alleen op "niet nul". 0 is identiek, 1 is verschillend, 2 is dat
diffzelf faalde. Een typefout in een pad als "de bestanden verschillen" behandelen levert zelfverzekerde onzin op. - Laat een patch altijd eerst proefdraaien.
patch -p1 --dry-runkost een seconde en vertelt je of het-p-niveau klopt voordat er iets geschreven wordt. - Gebruik
-bals je met de hand patcht, zodat de vorige versie naast het bestand ligt als je hem nodig hebt. - Lees elke regel waarin fuzz voorkomt. Een offset is prima. Fuzz betekent dat het bestand opgeschoven was en dat
patchgegokt heeft, en hij eindigt nog steeds met 0. - Zet
-F0in alles wat geautomatiseerd is. Een gefaalde hunk die je ziet is beter dan een vage hunk die je niet ziet. - Zoek naar
.rejen.origvoordat je de overwinning uitroept.find . -name '*.rej'is de laatste stap van elke patch die niet perfect schoon liep. - Gebruik het als test.
jouw-commando > actual.txtgevolgd doordiff -u expected.txt actual.txtis een complete regressietest: afsluitcode 0 is geslaagd, 1 is gezakt, en de uitvoer bij het zakken is al een leesbare uitleg van wat er veranderde. De meeste frameworks voor snapshottesten zijn dit met extra stappen. - Leid gekleurde uitvoer nooit naar een patchbestand. Gebruik
--color=autoen niets anders.
$ man 1 diff # every option, grouped by what it ignores
$ man 1 patch # the -p levels, fuzz, rejects and backups
$ info diff # the full GNU manual, much deeper than the man page
$ man 1 cmp # byte-level comparison
$ man 1 diff3 # three-way, and the origin of conflict markers
Naar boven9. Veelgemaakte fouten
9.1 Mythe versus werkelijkheid
| Mythe | Werkelijkheid |
|---|---|
"diff -rq bewees dat de restore klopt." |
Het bewees dat de inhoud van de bestanden overeenkomt. Permissies, eigendom, tijdstempels en symlinkstructuur zijn allemaal niet gecontroleerd. |
"patch eindigde met 0, dus de patch is goed toegepast." |
Hij is toegepast. Met fuzz kan hij toegepast zijn op code die niet meer overeenkomt met de context van de patch. |
| "Een afsluitcode ongelijk aan nul betekent dat de bestanden verschillen." | 1 betekent dat ze verschillen. 2 betekent dat diff faalde, meestal een verkeerd pad. |
| "Het hele bestand is veranderd, dus iemand heeft het herschreven." | Veel vaker zijn het de regeleindes. Controleer met file en cat -A voordat je iemand beschuldigt. |
"-p1 is een magisch getal." |
Het is het aantal voorste padonderdelen dat weggehaald moet worden. Lees de ----regel en bepaal waar je staat. |
"Een .rej-bestand betekent dat de patch mislukt is." |
Het betekent dat die hunks mislukt zijn. De rest is toegepast, dus de boom is nu half gepatcht. |
| "diff toont wat er veranderd is." | Het toont een manier om van A naar B te komen. Verplaats een blok code en diff meldt het als een verwijdering plus een ongerelateerde toevoeging (paragraaf 7.3). |
| "Ik heb beide kanten gesorteerd, dus de vergelijking is eerlijk." | De sorteervolgorde hangt af van de taalinstelling. Dezelfde lijst sorteert anders onder en_US.UTF-8 dan onder C, dus twee servers kunnen het oneens zijn over identieke gegevens. |
"-x 'cache/*' sluit de cachemap uit." |
Uitsluitpatronen komen overeen met de bestandsnaam, niet met het pad. Schrijf -x cache; alles met een schuine streep erin komt met niets overeen. |
| "Unified diff is iets van git." | Het werd rond 1990 aan Larry Walls patch toegevoegd, vijftien jaar voordat git bestond. |
9.2 Andere valkuilen om te vermijden
- Ongesorteerde uitvoer vergelijken.
diff <(ssh a 'dpkg -l') <(ssh b 'dpkg -l')zondersortaan beide kanten levert een diff van de volgorde op, niet van de inhoud. - De volgorde van de argumenten omdraaien.
diff nieuw oudis een geldig commando dat een patch oplevert die jouw werk ongedaan maakt. Het eerste bestand is altijd het "van". -Nvergeten bij een patch voor een hele boom. Zonder die vlag worden bestanden die maar aan een kant bestaan als "Only in" genoemd en niet meegenomen, dus de patch kan ze stilzwijgend niet aanmaken.- Een patch vanuit de verkeerde map toepassen. Het
-p-niveau en je werkmap zijn twee helften van dezelfde instelling. Krijg er een verkeerd en je krijgt "can't find file to patch", of erger: je patcht een bestand met dezelfde naam ergens anders. -wgebruiken om een verschil te laten verdwijnen. Witruimte is betekenisvol in Python, YAML, Makefiles en here-documents.-wis om te onderzoeken, niet om te concluderen.- Een patchbestand met de hand bewerken. De regeltellingen in de
@@-headers moeten kloppen met de regels die volgen, en niets waarschuwt je als dat niet zo is. - Een diff van een gegenereerd bestand vertrouwen. Geminificeerde assets, gecompileerde templates en cachebestanden verschillen voortdurend om redenen die niemand hoeft te zien. Sluit ze uit, of vergelijk de bronnen waar ze uit komen.
- Een uitsluiting als pad schrijven.
-x 'logs/*'en-x 'site/cache'komen allebei met niets overeen, stilzwijgend. Het patroon wordt getest tegen de kale naam van elke regel. - Twee servers vergelijken zonder de taalinstelling vast te zetten. Verschilt
LANGertussen, dan levertsortaan elke kant een andere volgorde op en is de diff louter ruis.LC_ALL=C sortaan beide kanten. .orig-bestanden in een webroot laten staan.helper.php.origwordt niet door PHP uitgevoerd, wat betekent dat de webserver hem als platte tekst kan uitleveren, inclusief alles wat erin staat. Ruim ze op.
10. Samenvatting
diff is vijftig jaar oud, zijn uitvoerformaat is de taal waarin elke code review geschreven is, en zijn waardevolste gebruik op een server heeft niets met programmeren te maken.
diffbeschrijft hoe je van het eerste bestand het tweede maakt. De volgorde doet ertoe, en de uitvoer is net zo goed bedoeld voorpatchals voor jou.- De afsluitcodes dragen de betekenis: 0 identiek, 1 verschillend, 2 er ging iets mis.
- Gebruik
-u. De header@@ -22,7 +23,7 @@is oude begin en aantal, nieuwe begin en aantal, en het verschil tussen die twee getallen is alles wat er erboven is toegevoegd of weggehaald. diff -rqbeantwoordt "wat is er verschillend tussen deze twee kopieën van de site?" in een enkele regel, en--no-dereferencemaakt het eerlijk over symlinks.- Sluit de ruis uit met
-xof-X, en onthoud dat het patroon namen matcht en geen paden. - Normaliseer voordat je vergelijkt: sorteer beide kanten, zet de taalinstelling vast met
LC_ALL=C, en gebruikjq -Sof iets vergelijkbaars voor gestructureerde gegevens. - diff kent geen verplaatst blok. Een bestand herordenen leest als een volledige herschrijving, en dat is het weten waard voordat je er een reviewt.
- Witruimte en regeleindes zijn de twee meest voorkomende loze alarmen.
fileencat -Aherkennen ze;--strip-trailing-cren-bomzeilen ze; het bestand repareren is beter. - Een patch is een unified diff in een bestand.
-pverteltpatchhoeveel voorste mappen hij moet weghalen, en dat hangt af van waar jij staat. - Eerst
--dry-run,-bom het origineel te bewaren,-Rom terug te draaien. Een patch is per ontwerp omkeerbaar. - Een offset is normaal. Fuzz is een waarschuwing: het bestand was opgeschoven,
patchnegeerde wat context om het passend te maken, en hij eindigt nog steeds met 0. Gebruik-F0waar niemand kijkt. - De conflictmarkeringen van git komen van
diff3, en het middelste deel is de gemeenschappelijke voorouder. - Moderne
patchweigert absolute paden en ontsnappingen met../, dus het echte risico is wat een patch binnen je boom gewoon mag doen. Lees hem; het is platte tekst. diff -rqcontroleert alleen de inhoud. Permissies, eigendom en symlinkstructuur hebben de vergelijking metfind -printfnodig.
Dit is het spiekbriefje dat je wilt bewaren:
diff -u a b unified: the format everything else speaks
diff -y a b side by side, add --suppress-common-lines
diff --color=auto -u a b colour on screen, plain when redirected
diff -rq DIR1 DIR2 WHICH files differ (the one to remember)
diff -rq --no-dereference ... ... and treat symlinks as symlinks
diff -rqN DIR1 DIR2 count missing files as differences too
diff -rq -x cache -x '*.log' exclude by NAME (never a path)
diff -rq -X skip.txt DIR1 DIR2 exclusion patterns from a file
-b ignore whitespace amount -w ignore all whitespace
-B ignore blank lines -i ignore case
-I REGEX ignore matching lines (timestamps, version stamps)
--strip-trailing-cr ignore Windows line endings
-U N N lines of context (default 3)
exit 0 identical | 1 different | 2 diff itself failed
diff -u old new > fix.patch make a patch
diff -ruN old/ new/ > release.patch make a whole-tree patch
patch -p1 --dry-run < fix.patch try it, change nothing
patch -p1 -b < fix.patch apply, keeping .orig
patch -p1 -R < fix.patch undo it
patch -p1 -F0 < fix.patch no fuzz: exact match or fail
grep '^+++' fix.patch which files does it touch?
find . -name '*.rej' -o -name '*.orig' ALWAYS, after patching
cmp a b first differing byte
comm -23 a b lines only in the first (sorted input)
diff3 -m mine base theirs three-way merge with conflict markers
diff <(cmd1 | sort) <(cmd2 | sort) compare output, not files
diff -u --label old --label new ... name the sides (pipes are /dev/fd/63)
diff <(LC_ALL=C sort a) <(LC_ALL=C sort b) same order on every machine
diff -u <(jq -S . a.json) <(jq -S . b.json) ignore key order
diff -u <(xxd a.bin) <(xxd b.bin) byte differences, readably
# before and after: what did that change actually do?
cmd | LC_ALL=C sort > before ; ...do the thing... ; cmd | LC_ALL=C sort > after
diff -u --label before --label after before after
# verify a restore: contents, then everything diff ignores
diff -rq --no-dereference /var/www/live /mnt/restore/live
diff <(cd /var/www/live && find . -printf '%p %m %u:%g\n' | sort) \
<(cd /mnt/restore/live && find . -printf '%p %m %u:%g\n' | sort)
Het gat tussen "de backup is gedraaid" en "de backup zet terug" is waar de meeste onaangename verrassingen wonen, en vergelijken wat er terugkwam met wat er stond is de goedkoopste manier om dat gat te dichten.
Naar boven

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












