Наконец, стоит заметить, что этот сценарий обнаруживает расхождения только в деревьях, не сообщая никаких подробностей о различиях в отдельных файлах. На самом деле он просто загружает и сравнивает двоичное содержимое соответствующих файлов в виде строк, давая простой результат «да/нет».
Когда мне нужны дополнительные сведения о фактических различиях в двух несовпавших файлах, я либо открываю их в редакторе, либо выполняю команду сравнения файлов на соответствующей платформе (например, fc в Windows/DOS, diff или cmp в Unix и Linux). Этот последний шаг не является переносимым решением, но для стоявших передо мною задач просто нахождение различий в дереве из 1400 файлов было значительно более важным, чем сообщение в отчете о том, в каких строках различаются эти файлы.
Конечно, поскольку в Python всегда можно вызвать команды оболочки, этот последний шаг можно автоматизировать, порождая при обнаружении различий команду diff или fc с помощью os.popen (или делать это после обхода, сканируя содержащуюся в отчете сводку). Вывод этих системных вызовов можно было бы поместить в отчет в первоначальном виде или оставить только наиболее важные его части.
Мы могли бы также открывать текстовые файлы в текстовом режиме, чтобы игнорировать различия, вызванные разными комбинациями символов завершения строк при передаче файлов между платформами, но не всегда ясно — действительно ли такие отличия должны игнорироваться (что если пользователь пожелает узнать, не изменились ли символы конца строки?). Например, после загрузки файла с веб-сайта с помощью сценария FTP, с которым мы встретимся в главе 13, сценарий diffall обнаруживает несоответствие между локальной копией файла и оригиналом на удаленном сервере. Продолжая исследования, я просто выполнил несколько инструкций в интерактивном сеансе Python:
>>> a = open(‘lp2e-updates.html’, ‘rb’).read()
>>> b = open(r’C:\Mark\WEBSITE\public_html\lp2e-updates.html’, ‘rb’).read()
>>> a == b
False
Эта проверка показывает, что двоичное содержимое локальной версии файла отличается от содержимого удаленной версии. Чтобы выяснить, обусловлено ли это различием способов завершения строк в Unix и DOS, я попробовал выполнить то же самое, но в текстовом режиме, чтобы перед сравнением символы окончания строк были приведены к стандартному символу \n:
>>> a = open(‘lp2e-updates.html’, ‘r’).read()
>>> b = open(r’C:\Mark\WEBSITE\public_html\lp2e-updates.html’, ‘r’).read()
>>> a == b
True
Теперь, чтобы отыскать различия, я выполнил следующие инструкции, которые проверяют содержимое символ за символом, пока не наткнутся на первое несоответствие (применение двоичного режима сохраняет различия):
>>> a = open(‘lp2e-updates.html’, ‘rb’).read()
> >> b = open(r’C:\Mark\WEBSITE\public_html\lp2e-updates.html’, ‘rb’).read()
> >> for (i, (ac, bc)) in enumerate(zip(a, b)):
… if ac != bc:
… print(i, repr(ac), repr(bc))
… break
37966 ‘\r’ ‘\n’
Этот результат означает, что в загруженном файле в байте со смещением 37 966 находится символ \r, а в локальной копии — символ \n. Эта строка в одном файле оканчивается комбинацией символов завершения строки в DOS, а в другом — символом завершения строки в Unix. Чтобы увидеть больше, можно вывести текст, окружающий несовпадение:
> >> for (i, (ac, bc)) in enumerate(zip(a, b)):
… if ac != bc:
… print(i, repr(ac), repr(bc))
… print(repr(a[i-20:i+20]))
… print(repr(b[i-20:i+20]))
… break
37966 ‘\r’ ‘\n’
‘re>\r\ndef min(*args):\r\n tmp = list(arg’
‘re>\r\ndef min(*args):\n tmp = list(args’
По всей видимости, я вставил символ завершения строки Unix в одном месте в локальной копии, там, где в загруженной версии находится комбинация символов завершения строки в DOS, — результат использования текстового режима в сценарии загрузки (который преобразует символы \n в комбинации \r\n) и многих лет использования ноутбуков и PDA, работающих под управлением Linux и Windows (вероятно, я внес это изменение, когда после редактирования этого файла в Linux я скопировал его в Windows в двоичном режиме). Такой программный код, как показано выше, можно было бы добавить в сценарий diffall, чтобы обеспечить более интеллектуальное сравнение текстовых файлов и вывод более подробной информации об отличиях в них.
Поскольку Python отлично подходит для обработки строк и файлов, можно пойти еще дальше и реализовать на языке Python сценарий, эквивалентный командам fc и diff. Фактически большая часть работы в этом направлении уже выполнена — эту задачу можно было бы существенно упростить, задействовав модуль difflib из стандартной библиотеки. Более подробные сведения о нем и примеры использования вы найдете в руководстве по библиотеке Python.
Можно было бы поступить еще умнее и не выполнять загрузку и сравнение файлов, отличающихся размерами; выполнять чтение файлов более мелкими порциями, чтобы уменьшить потребление памяти. Для большинства деревьев каталогов такие оптимизации излишни — чтение многомегабайтных файлов в строки в Python осуществляется очень быстро, а память постоянно освобождается сборщиком мусора в процессе работы.
Поскольку эти улучшения выходят за рамки задач данного сценария и размеров этой главы, его предоставляется выполнить любознательному читателю. (В этой книге официально отсутствуют упражнения для самостоятельного решения, но данное предложение выглядит именно так, не правда ли?) А теперь перейдем к исследованию способов реализации еще одной операции, часто применяемой к деревьям каталогов: поиск.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011