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 37799×
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.
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.
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.
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:
Znak | Význam |
a | V prvním souboru chybí dané řádky |
c | V souborech se liší dané řádky |
d | V 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.
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.
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. $
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 $
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ě.
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
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. $
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.
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.
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í, 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říkaz | Význam |
r | vybere variantu z pravého souboru |
l | vybere variantu z levého souboru |
e | vlastní řádek |
eb | vlastní řádek, do editoru budou předvloženy pravý a levý řádek |
el | vlastní řádek, do editoru bude předvložen levý řádek |
er | vlastní řádek, do editoru bude předvložen pravý řádek |
q | konec |
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.
Mezi GUI nástroje, které se používají pro porovnávání a mergování souborů patří TkDiff.
Uživatelé KDE asi budou znát také Kompare.