Главная » Программирование для UNIX » Интерактивная программа сравнения файлов: idiff

0

Часто бывает так, что в наличии имеются две версии файла, в чем-то  различных, при  этом каждая версия содержит часть  искомого файла. Обычно так случается, когда два пользователя вносят изменения неза висимо друг  от друга. Программа diff может рассказать, чем отличаются  файлы, но не поможет, если надо выбрать что-то  из первого фай ла, а что-то – из второго.

В  этом   разделе  будет   написана  программа   idiff («интерактивная diff»),  которая выводит  каждую порцию выходных данных diff  и предлагает  пользователю возможность выбрать  (по  своему  усмотрению) первый или  второй варианты или  же редактировать их. Команда idiff  выводит выбранные части в  правильном  порядке в  файл id– iff.out. То есть если рассматриваются два таких файла:

file1:                                      file2:

This  is                     This  is

a  test                                not  a  test of                                        of

your                                    our

skill                         ability.

and comprehension.

diff выводит:

$ diff file1  file2

2c2

< a  test

–––

> not  a test 4,6c4,5

< your

< skill

< and comprehension.

–––

> our

> ability.

$

Диалог с idiff может выглядеть следующим образом:

$ idiff file1  file2

2c2                                  Первое отличие

< a  test

–––

> not  a test

? >                                 Пользователь выбирает вторую (>) версию

4,6c4,5                        Второе отличие

< your

< skill

< and comprehension.

–––

> our

> ability.

? <                                 Пользователь выбирает первую (<) версию

idiff output  in  file idiff.out

$ cat  idiff.out        Вывод помещен в этот файл

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 <stdio.h>

#include  <ctype.h> char        *progname;

#define HUGE           10000     /*  большое  количество строк */ main(argc, argv)

int argc;

char  *argv[];

{

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

char  *diffout  = "idiff.XXXXXX";

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

fprintf(stderr,  "Usage:  idiff file1  file2\n"); exit(1);

}

f1  =  efopen(argv[1],  "r"); f2  =  efopen(argv[2],  "r");

fout  =  efopen("idiff.out",  "w"); mktemp(diffout);

sprintf(buf,"diff %s  %s  >%s",argv[1],argv[2],diffout); system(buf);

fin  =  efopen(diffout,  "r"); idiff(f1,  f2,  fin,  fout); unlink(diffout);

printf("%s  output   in  file  idiff.out\n",  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 = "idiff.XXXXXX";

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,  &from1,  &to1,  &cmd,  &from2,  &to2);

n = to1–from1  + to2–from2  + 1;  /*  число строк из diff */ if  (cmd == ‘c’)

n += 2;

else  if  (cmd  ==  ‘a’) from1++;

else  if  (cmd  ==  ‘d’) from2++;

printf("%s",  buf); while  (n––  >  0)  {

fgets(buf,  sizeof  buf,  fin); printf("%s",  buf);

}

do {

printf("?  "); fflush(stdout);

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

case  ‘>':

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

case  ‘<‘:

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,  "w"); ncopy(f1,  to1+1–from1,  ft); fprintf(ft,  "–––\n"); ncopy(f2,  to2+1–from2,  ft); fclose(ft);

sprintf(buf2,  "ed  %s",  tempfile); system(buf2);

ft  =  efopen(tempfile,  "r"); ncopy(ft,  HUGE,  fout); fclose(ft);

break;

case  ‘!': system(buf+1); printf("!\n"); break;

default:

printf("<  or  >  or  e  or  !\n"); break;

}

} while  (buf[0]!='<‘ &&  buf[0]!=’>’ &&  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––  > 0)

fgets(buf,  sizeof buf, fin);

}

ncopy(fin, n, fout)  /*  копировать  n строк из fin в fout  */ FILE  *fin, *fout;

{

char  buf[BUFSIZ];

while  (n––  > 0)  {

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

fputs(buf,  fout);

}

}

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

Сделаем важное замечание относительно двух программ, zap и idiff – и за одну,  и за другую самую  сложную работу  выполняет кто-то другой.  Эти  программы просто  предоставляют  удобный интерфейс для  другой программы, которая вычисляет нужную информацию. Использовать чью-то  работу  вместо  того,  чтобы делать ее самому, – это недорогой способ повышения производительности.

Упражнение 6.13.  Добавьте команду q в idiff следующим образом: ответ q< будет автоматически выбирать все оставшиеся ответы <, а q> будет автоматически выбирать все оставшиеся ответы >. ~

Упражнение 6.14.  Измените idiff так, чтобы  любые  аргументы diff передавались в diff; возможными кандидатами являются –b и –h. Измените idiff так, чтобы можно было указывать другой редактор:

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

Как  эти два изменения будут взаимодействовать друг с другом? ~

Упражнение 6.15.  Измените idiff так, чтобы вместо временного файла для  вывода diff  использовались popen и  pclose.  Как  это  повлияет на скорость и сложность программы? ~

Упражнение 6.16.  Команда diff обладает следующим свойством: если  один из ее аргументов является каталогом, она просматривает этот ка талог  в поиске файла с тем же  именем, которое указано вторым аргументом. Но если попробовать этот способ с idiff, она непонятным образом выходит из строя. Объясните, что происходит, и исправьте ошибку.  ~

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

По теме:

  • Комментарии