воскресенье, 1 марта 2009 г.

HuMan: diff

Виртуальная энциклопедия "Linux по-русски": новости, статьи, ссылки на материалы по операционной системе GNU/Linux.

Алексей Дмитриев, 24 февраля 2009

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

Маны команды diff ограничивапются перечислением опций команды, поэтому для ее освоения пришлось провести увлекательное расследование.

Команда diff без опций

Команда diff имеет мудреный вывод, чтобы разобраться с ним, составим два файла: cot.txt и cot2.txt:

$ cat cot.txt                 $ cat cot2.txt  кот                           кот собака                        крыса ящерица                       собака                               ящерица 

И запустим команду:

$ diff cot.txt cot2.txt 1a2 > крыса 

Попробуем разобраться с выводом команды. Выражение " > крыса" явно означает, что в правом (т.е. втором) файле есть лишнее слово: "крыса". Сложнее расшифровать выражение 1a2. Поскольку "крыса" вторая строка второго файла, то логично предположить, что цифра 2 означает номер строки, различной в сравниваемых файлах. Проверим предположение, поместив "крысу" на 4 строку:

$ diff cot.txt cot2.txt 3a4 > крыса 

Так и есть, отличается 4 строка второго файла. Появились и мысли насчет первой цифры, а также буквы "a".

Я понял это так:

1. Если считать эталонным первый файл, то 3 означает строку первого файла, после которой добавлена строка "крыса", являющаяся 4 строкой второго файла. Буква "a", по-видимому, означает первую букву в английском слове "added" (добавлено).

Или по-другому:

2. Если считать эталонным второй файл, то команда diff подсказывает, что надо сделать, дабы превратить первый файл во второй. Надо после третьей строки первого файла добавить (add) четвертую строку второго файла. Тогда файлы станут одинаковыми по содержанию.

Посмотрим, что произойдет, если мы поменяем сравниваемые файлы местами - первым поставим больший файл cot2.txt, а вторым меньший cot.txt:

$ diff cot2.txt cot.txt 4d3 > крыса 

Номера строк поменялись зеркально, что не удивительно, так как аналогично поменялось и расположение сравниваемых файлов. А вот буква "a" сменилась буквой "d". Рискнем предположить, что это первая буква слова "delete" - удалить. Итак, если удалить четвертую строку первого файла, то файлы станут одинаковыми. Цифра три в правой части выражения 4d3, вероятно, показывает, после какой строки второго файла следует различие в тексте файлов.

Осталось рассмотреть случай, когда в сравниваемых файлах одинаковое число строк. Давайте слегка изменим файл cot2.txt, заменив слово "собака" на выражение "собака серая", а "крысу" уберем совсем. Теперь в обоих файлах по три строки.

$ diff cot.txt cot2.txt 2c2 < собака --- > собака серая 

Программа указывает на разницу во вторых строках файлов. А букву "с" осмелюсь интерпретировать как первую букву слова "change" (заменить). То есть команда подсказывает, что если заменить вторую строку первого файла (cot.txt) на вторую строку второго файла (cot2.txt), то файлы станут одинаковыми.

Поменяем файлы местами:

$ diff cot2.txt cot.txt 2c2 < собака серая --- > собака 

Как видите, ничего не изменилось, только теперь следует заменить вторую строку файла cot2.txt на вторую строку файла cot.txt.

Появляются некоторые общие соображения:

1. Команда diff не считает какой-либо из файлов эталонным.

2. Команда diff дает подсказку, как сделать файлы одинаковыми.

3. Если вы хотите унифицировать файлы в сторону увеличения, то первым ставьте мЕньший файл.

4. Если вы хотите унифицировать файлы в сторону уменьшения, то первым ставьте бОльший файл.

5.Величина файла измеряется в количестве строк, а не в количестве байт. Если количество строк совпадает, то критерием служит количество слов; если оно совпадает, в дело идут символы.

(Узнать параметры файла можно при помощи команды wc).

Опция -с: Контекстный формат

-C число_строк --context=число_строк

При сравнении файлов зачастую нужно видеть не только различающиеся строки, но и соседние с ними, как "сверху", так и "снизу". Эти дополнительные строки называются контекстом. Опция -с позволяет видеть желательное число строк контекста. Если число_строк не указано, то, по умолчанию, показывается по три строки контекста до и после отличающейся строки. Поэтому нам придется увеличить число строк в наших образцовых файлах:

$ diff -c cot.txt cot2.txt                   *** cot.txt     2009-02-16 20:20:58.000000000 +0300  --- cot2.txt    2009-02-16 20:20:56.000000000 +0300 *************** *** 2,8 ****   собака   ящерица   белка ! зубр   попугай   рыба   змея --- 2,8 ----   собака   ящерица   белка ! бизон   попугай   рыба   змея 

Как видите, этот формат тоже требует разъяснения. Очевидно, что в заголовке указаны сравниваемые файлы, показано, какими условными знаками (звездочками или черточками) будут помечаться относящиеся к ним строки вывода; затем указано время модификации файла (похоже, что с точностью до наносекунды!). А вот что такое +0300, не берусь сказать, может быть выяснится по ходу дальнейших исследований.

Далее следует, так сказать, "тело" вывода. Среди соответствующих условных обозначений (звездочек и черточек) указаны интервалы приведенных строк через запятую (***2,8***), а знаком восклицания помечена строка, не совпадающая в сравниваемых файлах.

Добавим строку "крыса" в конец файла cot2.txt:

$ diff -C 1 cot.txt cot2.txt *** cot.txt     2009-02-16 20:20:58.000000000 +0300 --- cot2.txt    2009-02-16 22:37:11.000000000 +0300 *************** *** 4,6 ****   белка ! зубр   попугай --- 4,6 ----   белка ! бизон   попугай *************** *** 8 **** --- 8,9 ----   змея + крыса 

Из вывода можно понять, что во втором файле добавилась строка "крыса", но не хотел бы я разбираться с различиями многостраничных файлов, где строки состоят не из одного слова, а из настоящих предложений!

Давайте уберем первую строку первого файла "кот", и, заодно, уменьшим число строк контекста до 0:

$ diff -C 0 cot.txt cot2.txt *** cot.txt     2009-02-16 22:46:29.000000000 +0300 --- cot2.txt    2009-02-16 22:37:11.000000000 +0300 *************** *** 0 **** --- 1 ---- + кот *************** *** 4 **** ! зубр --- 5 ---- ! бизон *************** *** 7 **** --- 9 ---- + крыса 

Здорово! Получилось, что мы не убрали кота из первого файла, а добавили во второй! Явно здесь первый файл служит эталоном. Чтобы убедиться в этом, восстановим кота в первом файле и уберем во втором:

$ diff -C 0 cot.txt cot2.txt *** cot.txt     2009-02-16 22:50:36.000000000 +0300 --- cot2.txt    2009-02-16 22:50:44.000000000 +0300 *************** *** 1 **** - кот --- 0 ---- *************** *** 5 **** ! зубр --- 4 ---- ! бизон *************** *** 8 **** --- 8 ---- + крыса 

Ну и ну! Нет, эталоном здесь и пахнет. Чтобы понять этот вывод, нужно сильно напрячься. В первом файле, где "кот" есть, почему-то значится "- кот". Вероятно, нужно убрать кота, чтобы строки стали одинаковы. Но почему же тогда на восьмой строке второго файла, где живет "крыса", значится "+ крыса"? В конце концов, я нашел такое объяснение:

'!'   Строка, не совпадающая в двух файлах.  '+'  Строка, которой нет соответствия в первом файле.  '-'    Строка, которой нет соответствия во втором файле. 

Да-а, к этому формату нужно привыкнуть. Впрочем, есть еще унифицированный формат.

Опция --u: Унифицированный формат

-U число_строк --unified=число_строк

Унифицированный формат по умолчанию не выводит строк контекста:

$ diff -u cot.txt cot2.txt --- cot.txt     2009-02-16 22:50:36.000000000 +0300 +++ cot2.txt    2009-02-16 22:50:44.000000000 +0300 @@ -1,8 +1,8 @@ -кот  собака  ящерица  белка -зубр +бизон  попугай  рыба  змея +крыса 

Присмотревшись и вдумавшись, можно понять следующее:

1. В заголовке сказано, что минусами помечены строки из первого файла, а плюсами - из второго.

2. Формат даты такой же, как у контекстного формата.

3. Диапазоны строк указаны для обоих файлов и отмечены знаками двойной "собаки".

4. Слова, общие для двух файлов ничем не отмечены (или, если хотите, отмечены пробелом).

5. Знаком минус помечены строки, которые есть только в первом файле, как бы изъятые из первого файла, если считать его эталонным.

6. Знаком плюс помечены строки, которых нет в первом файле, как бы добавленные к нему.

Не знаю как вам, но мне этот формат кажется наиболее понятным.

Используя опцию -U число_строк (--unified=число_строк), можно просматривать нужное число строк контекста. Я этого делать не стану, так как мои примерные файлы слишком короткие для этого. Проделайте этот опыт самостоятельно с файлами подлиннее.

Опция -y или --side-by-side (бок-о-бок) Сравнительный формат

Этот формат команды diff выдает две колонки текста, что наиболее наглядно. По умолчанию длина строки 130 символов, что хорошо для длинных строк, для статьи же неудобно. Поэтому я сразу применю опцию -W 25, которая задаст ширину колонки в 25 символов.

$ diff -y -W 25 cot.txt cot2.txt  кот         < собака          собака ящерица         ящерица белка           белка зубр        |   бизон попугай         попугай рыба            рыба змея            змея             >   крыса 

Между двумя колонками текста находится узкая колонка условных обозначений. Эти обозначения могут быть следующими:

Пробел Соответствующие строки эквивалентны. Это означает, что либо они одинаковые, либо различие игнорировано из-за одной из опций '--ignore' .

'|' Соответствующие строки разные, и либо обе приведены полностью, либо обе не полностью.

'\' Соответствующие строки разные, первая строка приведена не полностью.

'/ ' Соответствующие строки разные, вторая строка приведена не полностью.

'<' Строка содержится только в первом файле.

'>' Строка содержится только во втором файле.

'(' Строка содержится только в первом файле, но различие игнорируется.

')' Строка содержится только вo втором файле, но различие игнорируется.

У опции -y есть, в свою очередь, несколько опций (или под-опций)

--left-column (левая колонка)

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

$ diff -y -W25 --left-column cot.txt cot2.txt кот           <        собака        (  ящерица       (        белка         (              зубр          |   бизон                       попугай       (                   рыба          (                       змея          (               >   крыса 

По-моему, все кристально ясно, может быть, я начинаю привыкать к этой команде?

Заметим в скобках, что опции --right-column (правая колонка) не существует.

--suppress-common-lines

Подавляет вывод строк, совпадающих в сравниваемых файлах:

$ diff -y -W25 --suppress-common-lines cot.txt cot2.txt  кот         < зубр        |   бизон             >   крыса 

Яснее ясного.

Жаль, что эта опция не может быть применена вместе с опцией --left-column, добавление которой никак не влияет на вывод команды.

Опция -e или --ed

Команда diff позволяет выводить результаты своей работы в формате командного файла для редактора ed:

$ diff -e cot.txt cot2.txt 8a крыса . 5c бизон . 1d 

Мы теперь достаточно знаем о синтаксисе команды diff, чтобы и без редактора понять, что означает этот вывод.

1. Инструкции показывают, как превратить первый файл во второй.

2. "8a крыса " означает добавить после восьмой строки строку "крыса ".

3. "5c бизон" означает заменить пятую строку строкой "бизон".

4. 1d значит удалить первую строку.

Однако, при желании, мы можем воспользоваться редактором ed для превращения файла cot.txt в файл cot2.txt.

Для этого сначала перенаправим вывод команды diff -e cot.txt cot2.txt в файл ed.script, который будет автоматически создан в рабочей директории:

$ diff -e cot.txt cot2.txt > ed.script

Затем запустим следующую конструкцию:

$ (cat ed.script && echo w) | ed - cot.txt

и проверим результат:

$ cat cot.txt  собака ящерица белка бизон попугай рыба змея крыса 

Полагаю, что "заклинание" в командной строке требует некоторого пояснения:

1. Применен программный канал, символом которого служит вертикальная черта (|). Вывод левой части канала служит вводом для правой части (команды ed - cot.txt).

2. Выражение в скобках в левой части программного канала, означает последовательное выполнение двух команд: cat ed.script и echo w, а знак && между ними означает логическое "и". Благодаря этому знаку вывод обеих команд объединяется:

$ (cat ed.script && echo w) 8a крыса . 5c бизон . 1d w 

Мы видим уже знакомый нам скрипт и приписанную командой echo в последней строке букву "w", которая служит командой редактора ed.

3. В правой части программного канала команда ed - cot.txt. Благодаря дефису редактор ed принимает стандартный ввод в качестве командного скрипта. Команды, сгенерированные программой diff -e, изменяют содержимое файла cot.txt, а команда w заставляет переписать этот файл с учетом изменений.

Не спрашивайте меня, почему все это так, и как можно иначе, сейчас не время рассматривать работу редактора ed - это тема отдельного исследования.

Важен факт, что файл cot.txt превратился (по своему содержанию, а не по названию) в файл cot2.txt.

Опция -f или --forward-ed (прямой ed)

Эта опция делает то же самое, что и опция -e, но выдает результат не с последней строки, а с первой, то есть в обратном порядке. Есть еще ряд отличий, в результате которых редактор ed не может использовать вывод этой опции в своей работе. Эта опция вообще бесполезна, и существует для совместимости со старыми версиями diff.

Опция -n или --rcs

Выводит результат в формате RCS - Системы управления изменениями (Revision Control System - RCS). Кто знает, что это такое, пусть читает руководство на русском языке.

Опция -D NAME

--ifdef=NAME

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

Описываемая опция позволяет производить слияние двух файлов - текстов на языке программирования Си. Результат работы 'diff' в этом формате будет содержать все строки обоих файлов. Строки общие для файлов отображаются только один раз; разные части разделяются с помощью директив препроцессора Cи '#ifdef NAME' или '#ifndef NAME', '#else', и '

Комментариев нет: