Как-то мне пришлось столкнуться с такой задачей: время от времени автоматически создается некий файл (далее — «новый файл»), который затем нужно сравнить с предыдущей версией этого файла (далее — «старый файл»). Если их содержимое различается, то надо скопировать информацию из нового файла в старый. Затем в любом случае новый файл надо удалить. Казалось бы, можно не производить сравнение, а всегда копировать информацию: даже если файлы совпадают, то от копирования хуже не станет. Но хуже все-таки станет, и вот почему: после копирования изменится дата старого файла — на ту дату, что была у нового. А для меня было важно, чтобы при совпадении файлов дата старого файла оставалась неизменной. Дело в том, что я собирался помещать этот файл на свою веб-страницу, основываясь именно на его дате: если она позже даты предыдущего обновления веб-страницы, то копировать, иначе — нет, так как с момента предыдущего обновления веб-страницы содержимое этого файла не изменилось. А если оно не изменилось, то какой смысл загружать файл повторно? Будет только лишняя трата времени, а этого-то я и хотел избежать.
Для сравнения содержимого файлов служит команда 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, то автоматически будет выбран режим двоичного сравнения¹).
Далее в первой строке идет символ <|>, обозначающий, что все выводимое командой 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.
¹Фигурнов В.Э. IBM PC для пользователя. Изд. 5-е, испр. и доп. М.: Финансы и статистика, 1994.