Інтерактивна програма порівняння файлів: idiff

Часто буває так, що в наявності є дві версії файлу, в чомусь різних, при цьому кожна версія містить частину шуканого файлу Зазвичай так трапляється, коли два користувача вносять зміни неза лежно один від одного Програма diff може розповісти, чим відрізняються файли, але не допоможе, якщо треба вибрати щось із перших фай ла, а щось – із другого

У цьому розділі буде написана програма idiff («інтерактивна diff»), яка виводить кожну порцію вихідних даних diff і пропонує користувачеві можливість вибрати (за своїм розсудом) перший або другий варіанти або ж редагувати їх Команда idiff виводить вибрані частини в правильному порядку в файл id-iffout Тобто якщо розглядаються два таких файлу:

file1:                                      file2:

This  is                     This  is

a  test                                not  a  test of                                        of

your                                    our

skill                         ability

and comprehension

diff виводить:

$ diff file1  file2

2c2

&lt a  test

–––

&gt not  a test 4,6c4,5

&lt your

&lt skill

&lt and comprehension

–––

&gt our

&gt ability

$

Діалог з idiff може виглядати наступним чином:

$ idiff file1  file2

2c2                                  Перша відмінність

&lt a  test

–––

&gt not  a test

? &gt                                 Користувач вибирає другий (&gt) версію

4,6c4,5                        Друга відмінність

&lt your

&lt skill

&lt and comprehension

–––

&gt our

&gt ability

? &lt                                 Користувач вибирає першу (&lt) версію

idiff output  in  file idiffout

$ cat idiffout Висновок поміщений в цей файл

This  is

not  a test of

your skill

and comprehension

$

Якщо замість < або> обраний відповідь e, то idiff викликає редактор ed, при цьому дві групи рядків уже лічені в редактор Якби другий відповіддю було e, то буфер редактора виглядав би наступним чином:

your skill

and comprehension

––– our ability

Те, що ed записує назад в файл, і потрапляє в кінцевий висновок

Нарешті, будь-яка команда може бути запущена під час виконання

idiff допомогою виходу в оболонку –cmd

Найскладніша з технічної точки зору завдання виконується функцією diff, яка вже була зроблена до нас Отож власне idiff залишається тільки розбирати вихідні дані diff і відкривати, закривати, читати і записувати відповідні файли в потрібний час У функції main програми idiff відкриваються файли і запускається процес diff:

/ * Idiff: інтерактивна diff * /

#include &ltstdioh&gt

#include  &ltctypeh&gt char        *progname

# Define HUGE 10000 / * велика кількість рядків * / main (argc, argv)

int argc

char  *argv[]

{

FILE *fin,  *fout, *f1,  *f2,  *efopen() char  buf[BUFSIZ],  *mktemp()

char  *diffout  = &quotidiffXXXXXX"

progname  =  argv[0] if  (argc  =  3)  {

fprintf(stderr,  &quotUsage:  idiff file1  file2\n&quot) exit(1)

}

f1  =  efopen(argv[1],  &quotr&quot) f2  =  efopen(argv[2],  &quotr&quot)

fout  =  efopen(&quotidiffout&quot,  &quotw&quot) mktemp(diffout)

sprintf(buf,&quotdiff %s  %s  &gt%s&quot,argv[1],argv[2],diffout) system(buf)

fin  =  efopen(diffout,  &quotr&quot) idiff(f1,  f2,  fin,  fout) unlink(diffout)

printf(&quot%s  output   in  file  idiffout\n&quot,  progname) exit(0)

}

Функція mktemp (3) створює файл, імя якого гарантовано отли чає від імені будь-якого існуючого файлу Аргумент функції mktemp перезаписується: шість X замінюються ідентифікатором процесу idiff і буквой1 Системний виклик unlink (2) видаляє названий файл з файлової системи

Перебором змін, відзначених diff, займається функція idiff Основна ідея досить проста: вивести частину вихідних даних diff, перескочити через непотрібні дані в одному файлі, потім скопіювати обрану версію з іншого Існує безліч утомливих подробиць, що роблять програму не такий маленькою, як хотілося б, але якщо розбити її на частини, нескладно зрозуміти, як вона працює

1 diffout являє собою покажчик на рядок символів, яка є константою Ця обставина викличе аварійне завершення програми, як тільки функція mktemp зробить спробу записи в область памяті, розподілену під константи Однак це не помилка авторів, просто в ті часи, коли ця програма була написана, ще можна було так поводитися з покажчиками на рядкові константи Щоб уникнути фатальної помилки через спроби запису в неправильний сегмент памяті, необхідно при використанні gcc вказати в командному рядку компілятора опцію-fwritable-strings – Прямуючи науч ред

idiff (f1, f2, fin, fout) / * обробка відмінностей * / FILE * f1, * f2, * fin, * fout

{

char  *tempfile = &quotidiffXXXXXX"

char  buf[BUFSIZ],  buf2[BUFSIZ],  *mktemp() FILE  *ft,  *efopen()

int cmd,  n, from1,  to1,  from2,  to2, nf1, nf2

mktemp(tempfile) nf1  =  nf2  =  0

while  (fgets(buf, sizeof  buf,  fin) =  NULL)  { parse(buf,  &ampfrom1,  &ampto1,  &ampcmd,  &ampfrom2,  &ampto2)

n = to1-from1 + to2-from2 + 1 / * Число рядків з diff * / if (cmd == c)

n += 2

else  if  (cmd  ==  a) from1++

else  if  (cmd  ==  d) from2++

printf(&quot%s&quot,  buf) while  (n––  &gt  0)  {

fgets(buf,  sizeof  buf,  fin) printf(&quot%s&quot,  buf)

}

do {

printf(&quot?  &quot) fflush(stdout)

fgets(buf,  sizeof  buf,  stdin) switch  (buf[0])  {

case  &gt’:

nskip(f1,  to1–nf1) ncopy(f2,  to2–nf2,  fout) break

case  &lt’:

nskip(f2,  to2–nf2) ncopy(f1,  to1–nf1,  fout) break

case  e:

ncopy(f1,  from1–1–nf1,  fout) nskip(f2,  from2–1–nf2)

ft  =  efopen(tempfile,  &quotw&quot) ncopy(f1,  to1+1–from1,  ft) fprintf(ft,  &quot–––\n&quot) ncopy(f2,  to2+1–from2,  ft) fclose(ft)

sprintf(buf2,  &quoted  %s&quot,  tempfile) system(buf2)

ft  =  efopen(tempfile,  &quotr&quot) ncopy(ft,  HUGE,  fout) fclose(ft)

break

case  !’: system(buf+1) printf(&quot!\n&quot) break

default:

printf(&quot&lt  or  &gt  or  e  or  \n&quot) break

}

} while  (buf[0]=&lt’ &amp&amp  buf[0]=&gt’ &amp&amp  buf[0]=e) nf1  = to1

nf2  = to2

}

ncopy (f1, HUGE, fout) / * Може вийти з ладу на занадто довгому файлі * / unlink (tempfile)

}

Функція parse здійснює банальну, але досить складну операцію з розбору рядків, що виводяться diff, виділяючи чотири номери рядків і команду (a, c або d) Ця функція складна, тому що diff може виводити по одному номеру рядка з кожного боку від букви команди, а може і по два

parse(s, pfrom1,  pto1, pcmd,  pfrom2,  pto2) char  *s

int *pcmd, *pfrom1,  *pto1,  *pfrom2, *pto2

{

#define a2i(p)  while  (isdigit(*s)) p = 10*(p)  + *s++  – 0

*pfrom1 =  *pto1  =  *pfrom2 =  *pto2  = 0 a2i(*pfrom1)

if (*s  ==  ,)  { s++ a2i(*pto1)

} else

*pto1  = *pfrom1

*pcmd  =  *s++ a2i(*pfrom2)

if (*s  == ,)  {

s++ a2i(*pto2)

} else

*pto2  = *pfrom2

}

Макрос a2i займається спеціалізованим перетворенням з ASCII в ціле число в чотирьох місцях, де це необхідно

Функції nskip і ncopy пропускають вказану кількість рядків або копіюють їх з файлу:

nskip (fin, n) / * пропустити n рядків у файлі fin * / FILE * fin

{

char  buf[BUFSIZ]

while  (n––  &gt 0)

fgets(buf,  sizeof buf, fin)

}

ncopy (fin, n, fout) / * копіювати n рядків з fin в fout * / FILE * fin, * fout

{

char  buf[BUFSIZ]

while  (n––  &gt 0)  {

if (fgets(buf,  sizeof buf,  fin) ==  NULL) return

fputs(buf,  fout)

}

}

У тому вигляді, як вона написана зараз, idiff, завершуючись при перериванні, залишає після себе декілька файлів в / tmp, що не дуже-то красиво У наступному розділі буде розказано про те, як перехоплювати переривання, щоб позбутися від тимчасових файлів, подібних наявними в даній ситуації

Зробимо важливе зауваження щодо двох програм, zap і idiff – і за одну, і за іншу найскладнішу роботу виконує хтось інший Ці програми просто надають зручний інтерфейс для іншої програми, яка обчислює потрібну інформацію Використовувати чиюсь роботу замість того, щоб робити її самому, – це недорогий спосіб підвищення продуктивності

Вправа 613Додайте команду q в idiff наступним чином: відповідь q < буде автоматично вибирати всі залишилися відповіді <, а q> буде автоматично вибирати всі залишилися відповіді> ~

Вправа 614Змініть idiff так, щоб будь-які аргументи diff передавалися в diff можливими кандидатами є-b і-h Змініть idiff так, щоб можна було вказувати інший редактор:

$ idiff-e другой5редактор file1 file2

Як ці два зміни будуть взаємодіяти один з одним ~

Вправа 615Змініть idiff так, щоб замість тимчасового файлу для виведення diff використовувалися popen і pclose Як це вплине на швидкість і складність програми ~

Вправа 616Команда diff має наступну властивість: якщо один з її аргументів є каталогом, вона переглядає цей ка талог в пошуку файлу з тим же імям, яке зазначено другим аргументом Але якщо спробувати цей спосіб з idiff, вона незрозумілим чином виходить з ладу Поясніть, що відбувається, і виправте помилку ~

Джерело: Керниган Б, Пайк Р, UNIX Програмне оточення – Пер з англ – СПб: Символ-Плюс, 2003 – 416 с, Мул

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*