Книга: UNIX — универсальная среда программирования

6.8 Диалоговая программа сравнения файлов: idiff

6.8 Диалоговая программа сравнения файлов: idiff

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

В этом разделе мы напишем программу idiff (диалоговая diff), которая предоставляет пользователю каждую порцию выходного потока diff и предлагает ему возможность выбора фрагментов "от и до" или их редактирования. Программа idiff помещает выбранные фрагменты в соответствующем порядке в файл idiff.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,6с4,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.
$

Если вместо < или > выдан ответ е, idiff вызывает ed с двумя группами уже прочитанных строк. Если вторым был ответ е, буфер редактора выглядел бы следующим образом:

your
skill
and comprehension.
---
our
ability.

Все, что пишется редактором обратно в файл, идет в окончательный выходной поток.

И, наконец, любая команда может быть выполнена внутри idiff с помощью временного выхода посредством !cmd.

Технически самая трудная часть работы diff, и она уже выполнена. Таким образом, в задачи idiff входит разбор выходного потока diff, открытие, закрытие, чтение и считывание соответствующих файлов в нужное время. Главная функция idiff поддерживает файлы и запускает процесс diff:

/* idiff: interactive diff */
#include <stdio.h>
#include <ctype.h>
char *progname;
#define HUGE 10000 /* large number of lines */
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 file2n");
  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.outn", progname);
 exit(0);
}

Функция mktemp(3) создает файл, имя которого гарантированно отличается от имени любого существующего файла. Mktemp переписывает свой аргумент: шесть символов X заменяются идентификатором процесса и буквой. Системный вызов unlink(2) удаляет поименованный файл из файловой системы.

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

idiff(f1, f2, fin, fout) /* process diffs */
 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, ftto1, &cmd, &from2, &to2);
  n = to1-from1 + to2-from2 + 1; /* #lines from 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); /* can fail on very long files */
 unlink(tempfile);
}

Функция parse выполняет рутинную, но тонкую работу по разбору строк, выдаваемых diff, извлекая четыре номера строки и команду (одну из а, с или d). При этом parse немного усложняется, так как 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) /* skip n lines of file fin */
 FILE *fin;
{
 char buf[BUFSIZ];
 while (n-- > 0)
  fgets(buf, sizeof buf, fin);
}
ncopy(fin, n, fout) /* copy n lines from fin to 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 так, чтобы некоторые аргументы idiff передавались к diff;-b и -h вероятные кандидаты. Выполните еще одну модификацию idiff, позволяющую определять другой редактор, как в команде

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

Как взаимодействуют эти две модификации?

Упражнение 6.15

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

Упражнение 6.16

Если один из аргументов diff -каталог, то в этом каталоге идет поиск файла с именем, заданным другим аргументом. Но если вы попробуете сделать то же самое с idiff, то она почему-то собьётся. Объясните, что в данном случае происходит, и исправьте дефект.

Оглавление книги


Генерация: 1.330. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз