Linux v příkazech - porovnávání souborů

Rozebereme metody porovnávání souborů, mergování a vytváření a aplikace patchů.

28.3.2006 06:00 | Jiří Václavík | přečteno 37143×

Historie nástrojů pro porovnávání obsahu textových souborů se píše již několik desítek let. Hlavními zástupci této kategorie nástrojů jsou diff a patch. diff hledá rozdíly ve dvou souborech a vytváří záplaty, které umí aplikovat inverzní příkaz patch.

Prosté porovnání

Asi nejjednodušším programem z této sady je cmp. Porovná 2 soubory, a jestliže se liší, vypíše kolikátý je první odlišný bajt. S přepínačem --print-bytes vypíše i tento odlišný znak. Jsou-li soubory shodné, cmp neprodukuje žádný výstup.

$ cmp soubor1 soubor2
soubor1 soubor2 differ: char 152, line 12
$

Pro tento účel lze použít i příkaz diff s parametrem -q.

Podrobné porovnání

Složitějším a značně pokročilejším příkazem pro porovnávání je diff. Neporovnává systémem znak po znaku, ale hledá společné a rozdílné části souborů. Příkaz diff už není pouze nástrojem pro získání informace, zda se soubory liší, ale zjišťuje v čem se liší.

diff také umí do jisté míry omezit, jaké rozdíly mezi soubory má zobrazit. Je schopen rozeznat například to, že se soubory liší pouze prázdnými řádky. Mezi další vlastnosti příkazu diff patří podpora několika výstupních formátů a schopnost pracovat s regulárními výrazy.

Ve výstupu příkazu diff se objevují rozdílné řádky z obou porovnávaných souborů, které jsou doprovázeny příslušnými značkami podle formátu zobrazení a typu rozdílnosti.

Abychom si mohli vlastnosti nástroje diff předvést na konkrétních příkazech, vytvoříme si dva soubory - original s obsahem

Jedno nedělní dopoledne, když se zvířata shromáždila, aby vyslechla
příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
Od nynějška bude farma zvířat obchodovat se sousedními farmami.
Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.

a následně soubor zmeneny:

Jedno nedělní dopoledne, když se zvířata shromáždila, aby vyslechla
příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.

Text byl převzat z knihy Farma zvířat.

Nyní zkusme najít všechny rozdíly v obou souborech. Srovnávání ručně znak po znaku je pomalé a navíc nejisté. Použijeme diff.

Máme k dispozici hned několik formátů výstupu.

Implicitní formát

Výstup porovnávání souborů original a zmeneny v implicitním formátu má následující strukturu:

změna
< řádek z 1. souboru
< řádek z 1. souboru
---
> řádek z 2. souboru
> řádek z 2. souboru
...

Změna obsahuje tři údaje a určuje místo v souboru, ve kterém byly nalezeny odlišnosti. První resp. třetí údaj určují příslušné řádky v prvním resp. druhém souboru a mohou obsahovat čísla řádků nebo rozsahy. Druhý údaj popisuje typ změny, který může nabývat tří hodnot:

ZnakVýznam
aV prvním souboru chybí dané řádky
cV souborech se liší dané řádky
dV prvním souboru jsou dané řádky navíc

Nyní příkaz diff zavoláme již s konkrétními parametry.

$ diff original zmeneny
3c3
< Od nynějška bude farma zvířat obchodovat se sousedními farmami.
---
> Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
$

Okamžitě vidíme, na kterém řádku je rozdíl.

Kontextový formát

Zejména v rozdílech zdrojových souborů uvítáme další formát výpisu změn. Přidáme-li přepínač -C s celočíselnou hodnotou, diff zobrazí okolo změn i daný počet okolních řádků.

$ diff -C1 original zmeneny
*** zmeneny   2006-03-12 16:07:39.000000000 +0100
--- original  2006-03-12 14:02:38.000000000 +0100
***************
*** 2,4 ****
  příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
! Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
  Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
--- 2,4 ----
  příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
! Od nynějška bude farma zvířat obchodovat se sousedními farmami.
  Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
$

Výstup začíná hlavičkami, za nimiž je část, z níž je patrné, v čem se oba texty liší. Každý řádek textu na výstupu začíná buď mezerou, znakem +, znakem - nebo vykřičníkem. Mezery značí shodné řádky a znaménka ty odlišné. Čísla uvozená znaky *** resp. --- znamenají rozsahy řádků pro příslušný soubor.

Sjednocený kontextový formát

Zaměníme-li -C za -U, nebude docházet k případnému opakovanému výstupu týchž řádků. Abychom spatřili rozdíl, musíme pozměnit ještě jiný řádek 2. souboru.

$ diff -U1 original zmeneny
--- original  2006-03-12 17:07:37.000000000 +0100
+++ zmeneny   2006-03-12 17:07:39.000000000 +0100
@@ -2,3 +2,3 @@
 příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
-Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
-Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
+Od nynějška bude farma zvířat obchodovat se sousedními farmami.
+Pochopitelně, že ně za komerčním účelem, ale jen proto, aby mohla získat
 nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.
$

Sloupcový formát

Dále lze porovnávat soubory také ve sloupcích. Značky pro upozornění na rozdíly jsou pak mezi oběma sloupci. Výstup je velmi široký, což je značná nevýhoda. Pomocí přepínače -W lze řádky useknout na požadovanou délku.

$ diff -y -W80 original zmeneny
Jedno nedělní dopoledne, když se zvíř   Jedno nedělní dopoledne, když se zvíř
příkazy, oznámil Napoleon, že se rozh   příkazy, oznámil Napoleon, že se rozh
Od nynějška bude Farma zvířat obchodo | Od nynějška bude farma zvířat obchodo
Pochopitelně, že ně za komerčním účel   Pochopitelně, že ně za komerčním účel
nejpotřebnější materiál. Větrný mlýn    nejpotřebnější materiál. Větrný mlýn
$

Porovnávání adresářů

Je-li jeden ze souborů předaných příkazu diff adresářem, hledá se v tomto adresáři soubor se stejným názvem jako uvedený textový soubor a ten se potom také porovnává. S přepínačem -r lze hledat v adresáři rekurzivně.

Nyní zadáme oba parametry jako názvy adresářů. Adresáře budou obsahovat tyto soubory:

$ ls verze0-98
soubor1  soubor2 soubor3 souborx
$ ls verze0-99
soubor1  soubor2 soubor3 soubory
$

Pomocí diff tyto adresáře porovnáme.

$ diff verze0-98 verze0-99
diff verze0-98/soubor2 verze0-99/soubor2
1c1
< obsah1
---
> obsah2
Pouze v verze0-98: souborx
Pouze v verze0-99: soubory
$

Vidíme, že se liší soubory verze0-98/soubor2 a verze0-99/soubor2 a rozdíly mezi nimi jsou zobrazeny. Navíc souborx je pouze v adresáři verze0-98 a soubory jen v verze0-99. V případě, že budeme chtít vypsat i změny souborů, které jsou pouze v jednom adresáři (například pro vytvoření patche), přidáme přepínač -N. Přidáním -r se bude porovnávat rekurzivně.

Další vlastnosti

Předáme-li příkazu diff přepínač -b, bude tolerovat odlišnosti v počtu mezer a tabulátorů a bude též ignorovat mezery na koncích řádků.

Pokud připíšeme -B, budou se ignorovat také odlišnosti výplývající z vložených prázdných řádků. Jsou-li však na řádku mezery nebo tabulátory, není brán jako prázdný. Abychom ignorovali i tyto řádky, musíme kromě -B použít i přepínač -b.

Přepínač -l připraví stránku pro tisk za pomoci příkazu pr.

Příkaz diff obsahuje také dvě volby ovlivňující rychlost hledání. -d zapíná důkladnější, ale také pomalejší hledání rozdílů. Rozdíly tak budou logičtěji uspořádané. Porovnáváme-li velké soubory, které se liší jen nepatrně, bude diff efektivnější s přepínačem -H.

diff podporuje regulární výrazy. Lze tak například omezit odlišnosti pouze na řádky, které nevyhovují danému regulárnímu výrazu. Pokud nebudeme chtít hlášení o řádcích, které končí číslicí, zadáme:

$ diff -I '[[:digit:]]$' original zmeneny

Vytváření patchů

Patch je souborem, obvykle s příponou .diff, který obsahuje seznam změn, jež je třeba provést, abychom z originálního souboru získali soubor změněný. Je to tedy jakýsi postup úprav.

Patch je mimo jiné cesta, jak distribuovat nové nebo upravené verze programů mezi vývojáři. Mají-li všichni vývojáři původní soubor (ve kterém mohou mít svoje drobné změny) a někdo z vývojářů chce svoji úpravu poskytnout ostatním, obvykle místo celého souboru pošle jen seznam změn, které udělal - tedy patch.

Vytvoříme patch pro upravení souboru original podle souboru zmeneny. .diff soubor získáme příkazem:

$ diff original zmeneny > change.diff

Pro vytváření patchů z více souborů se používá následující příkaz:

$ diff -Nr dir1 dir2 > change.diff

Po vytvoření patche je užitečné zobrazit si ho v textovém editoru a zkontrolovat, zda neobsahuje nějaké zbytečné úpravy jako je přidání bílých znaků. Pokud tyto změny obsahuje, měli bychom je zrušit a vygenerovat patch znovu.

Náš .diff soubor opravuje velikost úvodního písmena ve slově farma.

$ cat change.diff
3c3
< Od nynějška bude farma zvířat obchodovat se sousedními farmami.
---
> Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
$

Aplikace patchů

Pro aplikaci patche se používá příkaz patch. patch aplikuje .diff soubor na originální soubor.

Z minulého oddílu již máme vytvořený soubor change.diff, který nyní aplikujeme na soubor original. Následkem toho ve slově farma opravíme písmeno f na velké.

$ patch original change.diff
patching file original
$

Pokud je již v patchi uvedeno jméno původního souboru (patch musí používat nějaký formát, který jméno souboru uchovává), lze psát i

$ patch < change.diff

Vždy, když patch neví jak postupovat dále, zeptá se.

Ještě před aplikací patche se vytvoří záloha v podobě původního souboru s příponou implicitně .orig, která se dá změnit přepínačem -b.

Když máme vytvořen patch opačně - tj. ze změněného souboru na originální, máme k dispozici soubor originální a potřebujeme vytvořit soubor změněný, aplikujeme patch reverzně pomocí přepínače -R. Většinou však sám patch pozná, že aplikujeme opačný patch a zeptá se, zda ho má použít reverzně.

Problémy při aplikaci mohou nastat, pokud dojde k záměně mezer a tabulátorů. V takovém případě je třeba zadat -l pro ignorování bílých míst.

Pokud nelze z nějakého důvodu záplatu aplikovat, zálohuje se do .rej souboru.

Porovnání tří souborů

V případě, kdy dva lidé změní tentýž soubor a obě změny chceme promítnout ve výsledném patchi, použijeme diff3. U tohoto nástroje uveďme pouze příklad, takže případní zájemci se podívají na manuálovou stránku.

Pro ukázku vytvoříme dva soubory lišící se od originálu, který již máme. Soubor zmeneny1:

Jedno nedělní dopoledne, když se zvířata shromáždila, aby vyslechla
příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
Od nynějška bude farma zvířat obchodovat se sousedními farmami.
Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
ZMENA2nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.

A soubor zmeneny2:

Jedno nedělní dopoledne, když se zvířata shromáždila, aby vyslechla
příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
Od nynějška bude farma zvířat obchodovat se sousedními farmami.
Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
ZMENA3nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.

Nyní zobrazíme změny:

$ diff3 zmeneny1 original zmeneny2
====2
1:3c
3:3c
  Od nynějška bude farma zvířat obchodovat se sousedními farmami.
2:3c
  Od nynějška bude Farma zvířat obchodovat se sousedními farmami.
====
1:5c
  ZMENA2nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.
2:5c
  nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.
3:5c
  ZMENA3nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.
$

Změny jsou od sebe ve výpisu vždy odděleny řetězcem ====. Za ním může být pořadí odlišného souboru, jsou-li ostatní dva stejné. V každém oddílu jsou 3 řádky (pro každý soubor jeden) ve formátu pořadí_souboru:řádky a tyto řádky jsou vzápětí vypsány.

Hledání odlišných slov

Další a mnohdy přehlednéjší možností, jak zobrazovat rozdíly mezi soubory je wdiff. wdiff hledá nikoliv řádky, ale slova, ve kterých se soubory liší. Obecně lze říci, že slovo je nenulová posloupnost znaků mezi dvěma bílými znaky. Odlišnosti v bílých znacích jsou ignorovány. V praxi může použití wdiff vypadat takto:

$ wdiff original zmeneny
Jedno nedělní dopoledne, když se zvířata shromáždila, aby vyslechla
příkazy, oznámil Napoleon, že se rozhodl pro novou hospodářskou politiku.
Od nynějška bude [-farma-] {+Farma+} zvířat obchodovat se sousedními farmami.
Pochopitelně, že ne za komerčním účelem, ale jen proto, aby mohla získat
nejpotřebnější materiál. Větrný mlýn musí stát nad vším ostatním, řekl.
$

Slova, která jsou navíc v souboru original jsou ve výstupu implicitně uvozeny znaky [- a -] a slova, jež naopak v tomto souboru nejsou, znaky {+ a +}. Uvození lze změnit uvedením přepínačů -w, -x, -y a -z. Další možností je zapnuté zvýrazňování pomocí -t.

Lze též využít přepínačů -1 a -2, které zobrazují změny pouze jednostranně. Chceme-li zobrazit jen odlišná slova bez textu okolo, použijeme přepínač -3. Tyto přepínače lze kombinovat.

$ wdiff -3 original zmeneny
======================================================================
 [-farma-] {+Farma+}
======================================================================
$

Pro zobrazení statistik zahrnujících počty společných a odlišných slov připišme přepínač -s.

Mergování

Mergování, nebo-li slučování dvou souborů, se provádí příkazem sdiff. sdiff je příkaz s interaktivním rozhraním, který postupně prochází řádky obou souborů a při konfliktech se uživatele ptá, jak postupovat dále. Výsledkem po sdiff je soubor sloučený ze dvou souborů původních.

Budeme-li chtít vytvořit soubor vysledek sloučením souborů pravy a levy, použijeme toto volání příkazu sdiff:

$ sdiff -o vysledek pravy levy

V případě, že dojde k nějakým nejasnostem, budeme příkazem sdiff dotazováni, co se změnami. Máme tyto možnosti, jak odpovídat:

PříkazVýznam
rvybere variantu z pravého souboru
lvybere variantu z levého souboru
evlastní řádek
ebvlastní řádek, do editoru budou předvloženy pravý a levý řádek
elvlastní řádek, do editoru bude předvložen levý řádek
ervlastní řádek, do editoru bude předvložen pravý řádek
qkonec

U příkazů, které začínají na e je třeba mít nastavenu proměnnou prostředí $EDITOR, kde je uloženo jméno editoru, ve kterém budou řádky upravovány.

GUI nadstavby

Mezi GUI nástroje, které se používají pro porovnávání a mergování souborů patří TkDiff.

TkDiff

Uživatelé KDE asi budou znát také Kompare.

Kompare

Další zdroje

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1136