Kategória: Linux všeobecne

Zmenené: 9. september 2011

Rozdiely v súboroch

Nástroj diff používam na rôzne účely, ale dnes som si robil poriadok v niektorých projektoch a našiel som (samozrejme) rôzne verzie rovnakých súborov. Popri zisťovaní aké sú medzi nimi rozdiely som sa rozhodol podeliť s tým, čo a ako možno zistiť.

Nástroj diff

Nástroj diff slúži na zobrazenie (zistenie) rozdielov medzi dvomi súbormi (starý a nový). Výstupom programu diff je zoznam zmien (pridaný riadok, odobratý riadok, prípadne zmenený riadok), ktorý môže byť vo viacerých rôznych formátoch a býva doplnený aj susediacimi nezmenenými riadkami textu (kontext), ktorý slúži na lepšiu orientáciu v texte. Nástroj vyžaduje ako parametre dva súbory (alebo adresáre):

diff prvý druhý

Rada

V niektorých návodoch nájdete aj špecifikovanie, že prvý má byť starý súbor a druhý zase nový, ale toto je dôležité najmä pre vytváranie rozdielových súborov, zvaných záplaty (patches), určených na použitie s nástrojom patch, ktorý spomeniem na konci.

Okrem týchto dvoch povinných parametrov možno nástroju diff zadať mnoho volieb, z ktorých vyberám:

  • -a – vynucuje textové porovnanie súboru;
  • -E – ignoruje zmeny v odsadení tabulátorov;
  • -b – ignoruje zmeny v počte medzier;
  • -B – ignoruje zmeny v počte prázdnych riadkov;
  • -r – rekurzívne porovnanie podadresárov (pri porovnávaní adresárov).

Je to naozaj len krátky výpis. Podrobný popis volieb je dostupný v manuálovej stránke (diff(1)) alebo v pomocníkovi programu (diff --help).

Výstupné formáty diff

Nástroj diff poskytuje rôzne výstupné formáty, z ktorých každý má svoje výhody i nevýhody. Tu popíšem štyri:

  • štandardný,
  • kontextový,
  • zjednotený a
  • v dvoch stĺpcoch.

Štandardný formát

V tomto formáte výstupu vypisuje nástroj diff len riadky, ktoré sú v zadaných súboroch rozdielne a používa sa na rýchle zobrazenie zmien. Tento formát výstupu je použitý, ak nie je zadaný žiadny iný. Všeobecný formát takéhoto výstupu vyzerá takto:

popis zmeny
<riadok z prvého súboru
<riadok z prvého súboru
...
>riadok z druhého súboru
>riadok z druhého súboru
...

O akú zmenu konkrétne ide, možno zistiť z popisu zmeny, pričom možné popisy zmeny sú tri a vždy obsahujú číslo riadku alebo ich interval (čísla riadkov sú v intervale oddelené čiarkou) z prvého zadaného súboru, nasledovaný identifikátorom zmeny (a, c alebo d) a číslom (intervalom) riadkov z druhého súboru:

  • LaR v druhom súbore sú naviac riadky R, ktoré patria za riadok L prvého súboru;
  • FcT v druhom súbore boli zmenené riadky T, ktoré nahradzujú riadky F prvého súboru;
  • RdL v druhom súbore boli odstránené riadky R prvého súboru, ktoré by patrili za riadok L druhého súboru.

Kontextový formát

Druhým výstupným formátom je kontextový, v ktorom nástroj diff zobrazuje okrem zmien aj susediace (nezmenené) riadky. Takto možno získať lepší prehľad o umiestnení (a často aj význame) zmien. Kontextový formát výpisu možno zapnúť pomocou volieb -c, –context alebo -C. Prvá voľba použije tri kontextové riadky, druhej možno (pomocou =) a tretej treba zadať počet zobrazených riadkov kontextu. Kontextový formát poskytuje nie len lepšiu orientáciu ľuďom, ale je využívaný aj nástrojom patch, ktorý pomocou kontextu dokáže identifikovať správne umiestnenie zmien, napriek zmeneným číslam riadkov.

Príklady spustenia, všetky vyprodukujú rovnaký počet (3) riadkov kontextu:

diff -c prvý_súbor druhý_súbor
diff --context prvý_súbor druhý_súbor
diff --context=3 prvý_súbor druhý_súbor
diff -C 3 prvý_súbor druhý_súbor

Kontextový formát začína hlavičkou, ktorá identifikuje zadané súbory, ich označenie (*** alebo ), meno (prípadne aj s cestou) a ich časové údaje, napríklad:

*** prvý_súbor 2010-11-02 12:50:05.000000000 +0100
--- druhý_súbor 2010-11-02 12:49:57.000000000 +0100

Výpis jednotlivých zmien potom vyzerá takto:

***************
*** miesto zmeny ****
kontext
- riadok len v prvom súbore
kontext
! zmenený riadok
...
--- miesto zmeny ----
kontext
+ riadok len v druhom súbore
kontext
! zmenený riadok
...
--- miesto zmeny ---
...

Ak si to zmeny v súboroch vyžadujú, sú zobrazené pre oba súbory, ak nie, tak len pre príslušný súbor, ale vždy sa na začiatku zmeny vyskytujú riadky s udaním miesta zmeny. Miesto zmeny je interval (oddelený čiarkou) riadkov, ktorých sa zmena týka. Znaky *** respektíve udávajú, ktorého súboru sa zmena týka a zodpovedajú označeniu z hlavičky súboru. Jednotlivé riadky zmeny sú označené typom zmeny (!, + alebo -):

  • ! – riadky sa vyskytujú v oboch súboroch, ale boli zmenené;
  • + – riadky, ktoré sú len v druhom súbore;
  • - – riadky, ktoré sú len v prvom súbore.

Kontextové riadky nemajú na začiatku žiadnu značku (začínajú dvomi medzerami).

Zjednotený formát

Zjednotený (unified) formát spája výhody ponúknuté zahrnutím kontextu, ale produkuje menší výstup. Tento formát býva vyžadovaný mnohými projektmi. Výber zjednoteného formátu možno zapnúť pomocou prepínačov -u, –unified alebo -U, pričom pre zadanie počtu riadkov kontextu platia rovnaké pravidlá ako pre kontextový formát:

diff -u prvý_súbor druhý_súbor
diff --unified prvý_súbor druhý_súbor
diff --unified=3 prvý_súbor druhý_súbor
diff -U 3 prvý_súbor druhý_súbor

Aj zjednotený formát začína hlavičkou súboru, ktorá identifikuje porovnávané súbory, len ich označuje značkami a +++:

@@ miesto zmeny @@
kontext
+pridaný riadok
kontext
-odstránený riadok
kontext
...

Definícia miesta zmeny sa vždy skladá z dvoch rozsahov:

@@ -l,s +l,s @@

Symbol - označuje rozsah z prvého súboru a symbol + zase rozsah z druhého súboru. V oboch prípadoch sa jedná o interval čísel riadkov oddelený čiarkami. Zjednotený formát rozdielov zobrazuje len odstránené a pridané riadky:

  • - – riadky odstránené v druhom súbore (oproti prvému);
  • + – riadky pridané v novom súbore.

Zmenené riadky sú v tomto formáte vždy reprezentované vo forme odstráneného riadku z prvého súboru a pridaného riadku v druhom súbore.

Formát v dvoch stĺpcoch

Niekedy je dobré a hlavne prehľadné, zobraziť si zmeny pekne vedľa seba a práve toto poskytuje tento formát. Výpis v dvoch stĺpcoch je určený pre ľudí a nepoužíva sa na distribúciu zmien. Výpis v dvoch stĺpcoch možno zapnúť pomocou volieb -y alebo –side-by-side a vyprodukuje takýto výstup:

kontext                         kontext
                              > riadok len v starom súbore
kontext                         kontext
riadok len v novom súbore     <
kontext                         kontext
                              > odstránený riadok
kontext                         kontext
zmenený riadok 6              | nezmenený riadok 6
kontext                         kontext

V príklade je vidno, že i tento formát poskytuje kontext, hoci v skutočnosti poskytuje výpis celých súborov. Zmeny sú označené v druhom stĺpci a popisujú ako pridanie, či odstránenie riadkov, tak ich zmenu. V prípade pridania/odstránenia riadku, je tento zobrazený len na tej strane, v ktorom súbore sa nachádza, pri zmenených riadkoch sú zobrazené oba tvary riadkov (na oboch stranách).

Pomocou ďalších volieb je možné prispôsobiť si zobrazenie tohoto formátu, napríklad zadaním šírky výpisu (-W, –width), či vypísať len ľavý stĺpec (–left-column) alebo vynútiť zobrazenie len rozdielov (–suppress-comon-lines).

Ďalšie voľby

Okrem spomenutých volieb, ktoré udávajú formát výstupu sú zaujímavé aj ďalšie možnosti, z ktorých vyberám:

  • -i – ignoruje zmeny vo veľkosti písmen;
  • -E – ignoruje zmeny v tabulátoroch;
  • -b – ignoruje zmeny v medzerách;
  • -B – ignoruje zmeny v prázdnych riadkoch.

Nástroj patch

Nástroj patch slúži na aplikovanie zmien v súboroch a v tomto článku sa sústredím len na základy jeho použitia tak, aby vhodne doplnil rozprávanie o diff. História (a opodstatnenosť) tohoto nástroja siaha do doby, keď sa rýchlosť pripojenia k rozľahlej sieti (WAN) nemerala na megabity, ale na kilobity a veľkosť (resp. malosť) prenášaných súborov bola dôležitá. V súčasnosti sa môže jeho použitie zdať zbytočné, avšak tento nástroj poskytuje nie len spôsob ako príslušnú zmenu do súboru zahrnúť, ale aj ako ju zo súboru odstrániť, čím ponúka veľkú výhodu oproti uchovávaniu rozdielnych verzií súborov (v ktorých sa i tak skôr alebo neskôr mnoho ľudí stratí).

Základom aplikovania zmien je tzv. záplata (patch), čo nie je nič iné ako výstup nástroja diff. Hoci nástroj patch dokáže aplikovať všetky tri základné formáty výstupu diff, najvhodnejšie je používať kontextový alebo zjednotený formát, a to najmä kvôli poskytovanému kontextu. Kontext, ktorý sa na prvý pohľad môže zdať zbytočný totiž umožňuje aplikovať záplaty aj na zmenené súbory, kedy sa patch pomocou kontextu identifikovať nové umiestnenie zmien.

Vytvorenie záplaty

Najčastejšie sa používa zjednotený formát diff, ktorý sa jednoducho uloží do súboru, napríklad takto:

diff -u starý_súbor nový_súbor > rozdiel.diff

Na poradí súborov v podstate nezáleží, ale pri vyššie použitom poradí dávajú symboly plus a mínus lepší význam, keď plus znamená pridané a mínus zase odobrané v novom súbore… takto vytvorený rozdielový súbor možno jednoducho distribuovať, čo sa často využíva najmä pri vývoji softvéru.

Aplikovanie záplaty

Aplikovanie záplaty pomocou nástroja patch je prosté, pre niekoho však môže byť nezvyklé, že neočakáva súbor so záplatou ako parameter, ale číta ho zo svojho štandardného vstupu (stdin):

patch < rozdiel.diff

Ako patch zistí do ktorého súboru má zmeny aplikovať? Jednoducho, je to napísané v hlavičke (na začiatku súboru). Tu však niekedy môže nastať problém, najmä ak súbor rozdielov nebol vytvorený v rovnakom adresári, kde je pôvodný súbor, pretože v takom prípade bude patch hľadať súbor s celou cestou v hlavičke. Ak teda záplatu aplikujete hlbšie v adresárovej štruktúre (smerom hore to nefunguje) môžete využiť voľbu -p, ktorá z mena súboru v záplate odstraňuje zadaný počet lomiek (a tým aj adresárov). Často býva postačujúce odstrániť len prvú úroveň cesty:

patch -p1 < rozdiel.diff

Zaujímavou možnosťou je aj vyskúšanie aplikovania záplaty, ktoré všetko otestuje, vypíše výpisy akoby záplatu aplikoval, ale v skutočnosti nič nezmení:

patch -p1 --dry-run < rozdiel.diff

Odstránenie záplaty

Ako som už spomínal, záplatu možno pomocou nástroja patch nie len aplikovať, ale aj odstrániť. Tomuto procesu sa hovorí reverzné aplikovanie a slúži na to parameter -R:

patch -p1 -R < rozdiel.diff

…a máte súbor v stave, ako pred aplikáciou záplaty!

Riešenie problémov

Naozaj len stručne. S nástrojom diff si vlastne ani neviem predstaviť problémy, možno len pre úplnosť spomeniem, že v prípade chyby vracia hodnotu 2 (hodnota 1 neznamená problém, ale existujúce zmeny a hodnota 0 je ak sú súbory rovnaké).

S nástrojom patch sa už do problémov dostať môžete, najmä ak sa pokúsite aplikovať záplatu na súbor, ktorý sa nejako líši od toho, z ktorého bola záplata robená (vy alebo niekto iný ho zmenil). Nástroj patch je do istej miery schopný sa s týmito zmenami vysporiadať, ale nedokáže všetko. Ak sa s takýmto problémom stretnete, tak by mali menej skúsení používatelia hľadať inú verziu záplaty. Tí skúsenejší môžu skúsiť šťastie a zistiť, či nepostačí upraviť v záplate kontexty, prípadne zmeny…

Ostatné nástroje

Pre úplnosť už len zmienka, že spomenuté dva nástroje (diff a patch) nie sú jediné:

  • diff3 – ponúka možnosť porovnávania troch súborov;
  • kompare – grafický nástroj (pre KDE) na zobrazenie rozdielov
  • cmp – nezobrazuje rozdiely, ale dokáže zistiť rozdielnosť súborov;
  • svn diff – nie je samostatný nástroj ale slúži na vytváranie rozdielových súborov v prostredí subversion
  • quilt – komplexný nástroj na správu záplat