Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Проверка совпадения двух файлов в пакетном режиме

Мир ПК» 11/2006, 6/2007)

Как-то мне пришлось столкнуться с такой задачей: время от времени автоматически создаётся некий файл (далее — «новый файл»), который затем нужно сравнить с предыдущей версией этого файла (далее — «старый файл»). Если их содержимое различается, то надо скопировать информацию из нового файла в старый. Затем в любом случае новый файл надо удалить. Казалось бы, можно не производить сравнение, а всегда копировать информацию: даже если файлы совпадают, то от копирования хуже не станет. Но хуже всё-таки станет, и вот почему: после копирования изменится дата старого файла — на ту дату, что была у нового. А для меня было важно, чтобы при совпадении файлов дата старого файла оставалась неизменной. Дело в том, что я собирался помещать этот файл на свою веб-страницу, основываясь именно на его дате: если она позже даты предыдущего обновления веб-страницы, то копировать, иначе — нет, так как с момента предыдущего обновления веб-страницы содержимое этого файла не изменилось. А если оно не изменилось, то какой смысл загружать файл повторно? Будет только лишняя трата времени, а этого-то я и хотел избежать.

Для сравнения содержимого файлов служит команда fc. По умолчанию она выводит результат своей работы на экран: в первой строке — сообщение «Сравнение файлов» и далее имена сравниваемых файлов (на экране эта информация может занимать несколько строк, но логически это всё равно одна строка), а во второй строке при совпадении файлов выводится сообщение «FC: различия не найдены» (во всяком случае, именно так оно выглядит у меня в Windows 95 RUS; очевидно, в других версиях ОС текст этого сообщения может быть другим). Если же файлы не совпадают, то во второй и последующих строках выводится информация об обнаруженных различиях.

Таким образом, можно посмотреть, что вывела на экран команда fc, и, исходя из этого, решить, копировать ли информацию из нового файла в старый. Но мне хотелось этот процесс полностью автоматизировать, написав bat-файл, которому передавались бы имена двух файлов (нового и старого) и который вызывал бы команду fc для их сравнения и затем, анализируя результат её работы, либо копировал информацию из нового файла в старый, либо нет, а потом в обоих случаях удалял новый файл.

Тут и возникла проблема: обнаружилось, что команда fc не возвращает код выхода, зависящий от того, совпадают сравниваемые файлы или нет. В обоих случаях, как я проверил экспериментально, возвращается 0. И как же тогда проверить результат работы этой команды в bat-файле?

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

Ssed (super-sed, где sed расшифровывается как Stream EDitor) — это программа для обработки текста, так называемый текстовый фильтр. Она получает строки текста со стандартного ввода (или из файла), обрабатывает их в соответствии с заданной программой (скриптом) и выдаёт результат на стандартный вывод. Загрузить исполняемый файл ssed для Windows, а также исходные тексты и документацию можно со страницы http://sed.sourceforge.net/grabbag/ssed/.

Для меня в данной ситуации было важно то, что в скрипте для ssed может быть использована команда завершения работы с возвращением указанного кода выхода. Исходя из этого, я решил сделать так: направить выход команды fc на вход ssed, а скрипт для ssed составить так, чтобы при совпадении второй строки обрабатываемого текста (который является выходом команды fc) со строкой «FC: различия не найдены» ssed возвращал код выхода 255, а в противном случае — обычный код выхода (0, если при работе ssed не было никаких ошибок).

Скрипт получился очень простым:

2 s/^FC: различия не найдены$//
T
q255

Чтобы объяснить, как он работает, расскажу сначала в двух словах, как вообще ssed обрабатывает текст. В простейшем случае он читает одну строку текста, последовательно выполняет перечисленные в скрипте команды, выдаёт обработанную строку на выход, а затем все эти действия повторяются для следующей строки текста, и так далее, пока текст не кончится.

В первой строке вышеприведённого скрипта находится команда s (поиск и замена подстроки). Двойка перед ней означает, что она будет выполнена только для второй строки обрабатываемого текста.

Подстрока для поиска в этой команде — «^FC: различия не найдены$», где <^> обозначает начало строки, а <$> — конец (т.е. перед искомой подстрокой и после неё в строке ничего не должно быть). Как видим, это та самая строка, которая выводится командой fc при совпадении файлов (и это именно вторая строка, выводимая командой fc, поэтому и перед командой поиска и замены стоит 2).

Подстрока для замены в этой команде — пустая строка (что никакого значения не имеет, так как обработанный текст, полученный в результате работы ssed, в данном случае никак не используется: нам ведь надо только получить код выхода). Команда поиска и замены нужна в этом скрипте, только чтобы потом проверить, было ли совпадение при её выполнении или нет.

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

Команда q255 в третьей строке скрипта — это команда завершения работы с возвращением кода выхода 255. Как следует из описания предыдущих команд, эта команда выполняется, только если вторая строка обрабатываемого текста — это «FC: различия не найдены». Иначе эта команда не выполнится и выход из ssed произойдёт после просмотра всего поданного ему на вход текста; в этом случае ssed вернёт код 0 (обычный код выхода).

Рассмотренный скрипт может быть записан в одну строку (это позволяет задавать его прямо в командной строке при запуске ssed):

2s/^FC: различия не найдены$//;T;q255

Итак, мне оставалось только написать bat-файл, о котором говорилось в начале статьи. Этот файл (я назвал его cmp_move.bat) также получился очень простым:

@fc %1 %2 | ssed -n "2s/^FC: различия не найдены$//;T;q255"
@if not errorlevel 255 copy /y %1 %2
@del %1

Символ @ перед каждой его строкой нужен, чтобы эти строки не печатались при выполнении bat-файла.

В первой строке вызывается команда fc, которой в качестве входных параметров передаются имена нового и старого файлов, указанные при запуске bat-файла (они обозначаются соответственно %1 и %2). Замечу, что у меня сравниваемые файлы — текстовые, а для сравнения двоичных файлов надо указать опцию /b, т.е. вместо «fc %1 %2» должно быть «fc /b %1 %2» (хотя если расширения сравниваемых файлов — exe, com, sys, obj, lib или bin, то автоматически будет выбран режим двоичного сравнения1).

Далее в первой строке идёт символ <|>, обозначающий, что всё выводимое командой fc будет передано на вход следующей команды. В данном случае это команда вызова ssed с опцией -n (для отключения вывода — чтобы ssed не печатал обработанный им текст) и уже знакомым нам скриптом.

Во второй строке анализируется код выхода, возвращённый последней выполненной командой (т.е. командой вызова ssed), и если он меньше 255 (файлы не совпадают), то вызывается команда copy для копирования содержимого нового файла в старый. Опция /y в этой команде отключает выдачу запроса о подтверждении перезаписи файла.

В третьей строке находится команда del, удаляющая новый файл.

Замечу, что рассмотренный bat-файл обязательно должен быть в альтернативной кодировке DOS (CP866), ведь именно в такой кодировке команда fc выводит строку «FC: различия не найдены».

Впоследствии проверку совпадения файлов мне довелось использовать при решении ещё одной задачи — написании bat-файлов для автоматического тестирования своих программ. Расскажу и об этом.

Например, есть некая программа program.exe, при запуске которой в командной строке указываются имена двух файлов; она обрабатывает данные из первого файла и записывает результат во второй. Есть тесты, каждый из которых состоит из файла с данными и файла с результатом правильной обработки этих данных. Допустим, таких тестов два, в первом эти файлы называются test_1.txt и right_1.txt, а во втором соответственно test_2.txt и right_2.txt. Также известно, что при обработке этих тестовых данных программа должна завершать работу с нулевым кодом выхода.

Нужно написать bat-файл, проверяющий, действительно ли эта программа при обработке файла test_1.txt выдаёт такой же результат, как в файле right_1.txt (аналогично для файлов test_2.txt и right_2.txt), и действительно ли она при этом завершает работу с нулевым кодом выхода.

Вот выполняющий эту задачу bat-файл; он, как и предыдущий, должен быть в альтернативной кодировке DOS (CP866).

@echo off

program.exe source_1.txt result_1.txt
if errorlevel 1 goto error1
fc right_1.txt result_1.txt | ssed -n "2s/^FC: различия не найдены$//;T;q255"
if not errorlevel 255 goto error1
del result_1.txt

program.exe source_2.txt result_2.txt
if errorlevel 1 goto error2
fc right_2.txt result_2.txt | ssed -n "2s/^FC: различия не найдены$//;T;q255"
if not errorlevel 255 goto error2
del result_2.txt

echo Test passed!
goto end

:error1
echo Error in test 1!
goto end

:error2
echo Error in test 2!

:end

Он выводит сообщение «Test passed!», если тестирование прошло успешно, и «Error in test 1!» или «Error in test 2!», если соответствующий тест не пройден (т.е. тестируемая программа завершилась с ненулевым кодом выхода и/или результат её работы оказался не таким, каким должен быть). Если не пройден первый тест, то второй не обрабатывается.

При прохождении каждого теста создаётся временный файл с результатом работы программы (для первого теста — result_1.txt, для второго — result_2.txt). Он удаляется, если тест пройден успешно, иначе — остаётся, и можно впоследствии сравнить его с правильным результатом для выявления конкретного различия между ними.

Думаю, нетрудно догадаться, как адаптировать этот bat-файл для аналогичной ситуации, но, скажем, для другого количества тестов или для случая, когда программа выдаёт не один файл с результатами работы, а несколько. Если тестируемая программа выдаёт не текстовые результаты, а двоичные, то для команды fc в bat-файле должна быть указана опция /b.

Дополнение

За время, прошедшее после написания статьи «Проверка совпадения двух файлов в пакетном режиме» («Мир ПК» №11/06), я обнаружил следующее:

  1. В Windows XP SP2 RUS в отличие от Windows 95 RUS команда fc возвращает код выхода, зависящий от результата сравнения файлов: 0 при совпадении и 1 при несовпадении. Так что если требуется проверить совпадение файлов в этой ОС, то описанный в моей статье метод с использованием ssed становится ненужным.
  2. Этот метод с использованием ssed не работает в Windows XP SP2 RUS (даёт ответ, что сравниваемые файлы различны, даже в том случае, когда на самом деле они одинаковы). Дело оказалось в команде запуска ssed:
    ssed -n "2s/^FC: различия не найдены$//;T;q255"
    
    Как я выяснил экспериментальным путём, при выполнении этой команды ОС перекодирует содержащийся в ней скрипт из кодировки DOS (CP866) в Windows-1251, прежде чем передать его ssed’у. Именно это перекодирование и нарушает работу программы.

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

  1. Можно вынести скрипт (строку «2s/^FC: различия не найдены$//;T;q255» — без кавычек, разумеется) в отдельный файл в кодировке DOS (CP866). Пусть имя этого файла — скажем, check_eq.sed, тогда команду запуска ssed изменяем так:
    ssed -n -f check_eq.sed
    
    Теперь никакого перекодирования скрипта происходить не будет, и он будет работать правильно.
  2. Можно не создавать дополнительный файл со скриптом, а заменить русские буквы на их шестнадцатеричные коды в кодировке DOS (CP866). Это сделает скрипт нечувствительным к перекодированию. Команда запуска ssed тогда будет выглядеть так:
    ssed -n "2s/^FC: \xE0\xA0\xA7\xAB\xA8\xE7\xA8\xEF \xAD\xA5 \xAD\xA0\xA9\xA4\xA5\xAD\xEB$//;T;q255"
    
    Но, как видите, длина скрипта при этом заметно возрастает. В результате строка bat-файла, содержащая данную команду запуска ssed, может стать излишне длинной, а, по моим наблюдениям, в Windows XP при обработке слишком длинной строки bat-файла командным процессором command.com принимаются во внимание только первые 127—128 её символов (это уже после подстановки значений переменных в строке).

1Фигурнов В.Э. IBM PC для пользователя. Изд. 5-е, испр. и доп. М.: Финансы и статистика, 1994.

Другие мои статьи о bat-файлах:

Страница Ивана Рощина > Статьи >