Введение в POSIX'ивизм

     крапленые карты |   

Сравнение, объединение и деление файлов


Следующая важная группа операций над контентом файлов - сравнение файлов по содержанию и различные формы объединения файлов и их фрагментов. Начнем со сравнения. Простейшая команда для этого - cmp в форме

$ cmp file1 fil2

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

file1 file2 differ: char 27, line 4

Это означает, что различия между файлами начинаются с 27-го от начала файла символа (включая пробелы, символы конца строк и т.д.), который имеет место быть в строке 4. С помощью опций -l и -z можно заставить команду cmp вывести номера всех различающихся символов в десятичном или шестнадцатеричном формате, соответственно.

Более информативный вывод обеспечивает команда diff. Она также осуществляет построчное сравнение двух файлов, но выводит список строк, в которых обнаружены отличия. Например, для двух файлов вида

$ less file1 line 1 line 2 line 3 line 4 line 5

и

$less file2 line 1 line 2 line 3 line 3a line 4 line 5

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

$ diff file1 file2 3a4 > line 3a

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

  • a (от append) указывает на строку, отсутствующую в первом файле, но присутствующую во втором;


  • c (от change) фиксирует строки с одинаковым номером, но разным содержанием;

  • d ( от delete) определяет строки, уникальные для первого файла.


  • То есть в данном примере для преобразования file1 в file2 в него после строки 3 должна быть вставлена строка 4 из второго файла, что символизирует вторая линия блока - > line 3a, где > означает строку из первого сравниваемого файла. Если же аргументы команды diff дать в обратном порядке, вывод ее будет выглядеть следующим образом:

    $ diff file2 file1 4d3 < line 3a

    показывающим, что для достижения идентичности из file2 должна быть удалена четвертая строка (< line 3a, где < означает строку из второго файла). Если же произвести сравнение file1 с file3, имеющим вид

    $ less file3 line 1 line 2 line 3a line 4 line 5

    то вывод команды

    $ diff file1 file3 3c3 < line 3 --- > line 3a

    будет означать необходимость замены третьей строки из file1 (символ ) на третью строку из file3 (символ >).

    Такая форма вывода команды diff называется стандартной. С помощью опции -c можно задать т.н. контекстную форму вывода, при которой на экран направляется не только различающиеся строки, но и строки, их окружающие (то есть контекст, в котором они заключены):

    diff -c file1 file2 *** file1 Sun May 12 11:44:44 2002 --- file2 Mon May 13 15:17:27 2002 *************** *** 1,5 **** --- 1,6 ---- line 1 line 2 line 3 + line 3a line 4 line 5

    Количество строк контента задается опцией -C:

    diff -C 1 file1 file2 ttyv1 *** file1 Sun May 12 11:44:44 2002 --- file2 Mon May 13 15:17:27 2002 *************** *** 3,4 **** --- 3,5 ---- line 3 + line 3a line 4

    В этом примере значение опции -C (единица) предписывает вывод по одной строке контекстного окружения вокруг различающейся строки. Сами же различающиеся строки помечаются следующим образом: знаком - (минус, или дефис) - строки, подлежащие удалению из первого файла, знаком + (как в примере) - строки, которые должны быть добавлены, знаком ! - просто различающиеся строки.

    Кроме контекстного формата, используется еще и вывод в унифицированном формате, что предписывается опцией -U [значение], в качестве значения указывается число строк.


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

    С помощью многочисленных опций команды diff сравнение файлов может быть детализовано и конкретизировано. Так, опция -b предписывает игнорировать "пустые" символы пробелов и табуляции в конце строк, а опция -w - вообще "лишние" пробелы (и те, и другие обычно имеют случайное происхождение). При указании опции -B игнорируются пустые строки, то есть не содержащие никаких иных символов, кроме перевода каретки; строки с символами табуляции или пробела как пустые не рассматриваются, для их игнорирования требуется опция -w. Благодаря опции -i при сравнении не принимается во внимание различие регистров символов, а опция -I regexp определяет регулярные вырвжения, строки с которыми также игнорируются при сравнении.

    В качестве аргументов команды diff (одного или обоих) могут выступать также каталоги. Если каталогом является только один из аргументов, для сравнения в нем отыскивается файл, одноименный второму аргументу. Если же оба аргумента суть каталоги, в них происходит сравнение всех однименных файлов в алфавитном порядке (вернее, в порядке ASCII-кода первого символа имени, разумеется). Благодаря опции -r сравнение файлов может осуществляться и во вложенных подкаталогах.

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

    В принципе, команда diff и придумана была именно для сравнения файлов исходников, над которыми ведут работу несколько (в пределе - неограниченное количество, как в случае с Linux) человек. Однако невозбранно и ее использование в мирных целях - то есть для сравнения просто повествовательных текстов. Единственное, что следует понимать при этом абсолютно ясно - то, что diff выполняет именно построчное сравнение.


    То есть: сравнение последовательностей символов, ограниченных символами конце строки с обеих сторон. И, соответственно, непрерывная абзацная строка в стиле emacs и vi - совсем не то же самое, что строка, образуемая в редакторе joe на границе экрана. Впрочем, это - вопрос, к которому еще не раз придется возвращаться.

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

    Существуют и средства для сравнения сжатых файлов. Это - zcmp и zdiff. Подобно командам просмотра, ими просто вызываются соотвествтующие команды cmp и diff. И потому использование их не имеет никаких особенностей.

    От вопроса сравнения файлов плавно перейдем к рассмотрению способов их объединения. Для этого существует немало команд, из которых по справедливости первой должна идти команда cat, поскольку именно сие есть ее титульная функция (cat - от concatenation, сиречь объединения). Ранее уже упоминалось, что она способна добавлять информацию со стандартного ввода в конец существующего файла. Однако дело этим не ограничивается. В форме

    $cat file1 file2 ... file# > file_all

    она создает новый файл, включающий в себя содержимое всех файлов-аргументов (и именно в том порядке, в каком они приведены в командной строке). Операция, казалось бы, нехитрая - однако представьте, сколько действий потребовалось бы в текстовом процессоре (например, в Word'е) для того, чтобы создать синтетический вариант из полутора десятков фрагментов, раскиданных по разным каталогам?

    А вот команда patch выступает в качестве диалектической пары для команды diff, именно она вносит в файл те изменения, которые документируются последней.


    Выглядит эта команда примерно так:

    $patch file1 diff_file

    в ответ на что последует нечто вроде следующего вывода:

    Hmm... Looks like a normal diff to me... Patching file file1 using Plan A... Hunk #1 succeeded at 4. done

    В результате исходная версия file1 будте сохранена под именем file1.orig, а сам он преобразован в соответствие с описанием diff-файла. Возможна и форма

    patch < diff_file

    В этом случае команда patch попытается сама определить имя файла-оригинала, и, если это ей не удастся, даст запрос на его ввод. Обращаю внимание на символ перенаправления ввода, поскольку если его опустить, имя dif-файла будет воспринято как первый аргумент команды (то есть имя файла-оригинала).

    В качестве второго аргумента команды patch могут использоваться dif-файлы не только в стандартном, но и в контекстном или унифицированном формате. Это следует указать посредством опции -c или -u, соответственно.

    Сочетание команд diff и patch очень широко используется при внесении изменений в исходные тексты программы. Так, если мы обратимся к коллекции портов FreeBSD, то в большинстве каталогов портированных программ можно обнаружить подкаталог files (например, /usr/ports/editors/joe-devel/files), содержимое которого как раз и есть те патчи, которые должны накладываться на исходные тексты программы для ее корректной сборки, установки и функционирования во FreeBSD. Сам же исходный текст при этом в неизменном, то есть распространяемом разработчиком, виде получается с его сайта (или любого другого сервера, в том числе и каталога distfiles ftp-сервера проекта FreeBSD.

    Не менее часто, чем в слиянии, возникает и необходимость в разделении файлов на части. Цели этой служит команда split. Формат ее:

    $ split [options] filename [prefix]

    В результате исходный файл будет разбит на несколько отдельных файлов вида prefixaa, prefixab и так далее. Значение аргумента prefix по умолчанию - x (то есть без его указания итоговые файлы получат имена xaa, xab и т.д.).

    Опции команды split задают размер выходных файлов - в байтах (опция -b) или количестве строк (опция -l).


    Первой опцией в качестве единицы, кроме байтов, могут быть заданы также килобайты или мегабайты - добавлением после численного значения обозначения k или m, соответственно.

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

    $ split -b 1474560 arch_name

    она обеспечит разбиение архива на части, какждая из которых может быть записана на стандартную трехдюймовую дискету. А посредством

    $ split -b 650m arch_name

    архив можно подготовить к записи на носители CD-R/RW. Напомню, что именно командой split создаются файлы дистрибутива FreeBSD. Легко догадаться, что обратное слияние таких фрагментированных файлов можно выполнить командой cat.

    Однако этим возможности BSD-реализации команды split не исчерпываются. С помощью опции -p файл может быть разделена на фрагменты, ограниченные строками, сорержащими текст, приведенный в качестве значения шаблона. Так, командой

    $ split -p Глава filename chapter-

    текст произвольной длинны будет легко разбит на файлы, соответствующие отдельным главам, которым будут присвоены имена chapter-aa и далее. В качестве значения опции -p могут использоваться тэги HTML или символы разметки TeX, а также регулярные выражения.

    Linux-реализация команды split таким свойством не обладает. Однако взамен этому в Linux есть команда csplit, именно для разделения файла по шаблону и предназначенная.


    Содержание раздела