Рубрики
Без рубрики

filecmp – Сравнить файлы

Автор оригинала: Doug Hellmann.

Цель:

Сравните файлы и каталоги в файловой системе.

Модуль filecmp включает функции и класс для сравнения файлов и каталогов в файловой системе.

Пример данных

В примерах этого обсуждения используется набор тестовых файлов, созданных filecmp_mkexamples.py .

filecmp_mkexamples.py

import os


def mkfile(filename, bodyNone):
    with open(filename, 'w') as f:
        f.write(body or filename)
    return


def make_example_dir(top):
    if not os.path.exists(top):
        os.mkdir(top)
    curdir  os.getcwd()
    os.chdir(top)

    os.mkdir('dir1')
    os.mkdir('dir2')

    mkfile('dir1/file_only_in_dir1')
    mkfile('dir2/file_only_in_dir2')

    os.mkdir('dir1/dir_only_in_dir1')
    os.mkdir('dir2/dir_only_in_dir2')

    os.mkdir('dir1/common_dir')
    os.mkdir('dir2/common_dir')

    mkfile('dir1/common_file', 'this file is the same')
    os.link('dir1/common_file', 'dir2/common_file')

    mkfile('dir1/contents_differ')
    mkfile('dir2/contents_differ')
    # Update the access and modification times so most of the stat
    # results will match.
    st  os.stat('dir1/contents_differ')
    os.utime('dir2/contents_differ', (st.st_atime, st.st_mtime))

    mkfile('dir1/file_in_dir1', 'This is a file in dir1')
    os.mkdir('dir2/file_in_dir1')

    os.chdir(curdir)
    return


if __name__  '__main__':
    os.chdir(os.path.dirname(__file__) or os.getcwd())
    make_example_dir('example')
    make_example_dir('example/dir1/common_dir')
    make_example_dir('example/dir2/common_dir')

При запуске сценария создается дерево файлов в каталоге example :

$ find example | sort

example
example/dir1
example/dir1/common_dir
example/dir1/common_dir/dir1
example/dir1/common_dir/dir1/common_dir
example/dir1/common_dir/dir1/common_file
example/dir1/common_dir/dir1/contents_differ
example/dir1/common_dir/dir1/dir_only_in_dir1
example/dir1/common_dir/dir1/file_in_dir1
example/dir1/common_dir/dir1/file_only_in_dir1
example/dir1/common_dir/dir2
example/dir1/common_dir/dir2/common_dir
example/dir1/common_dir/dir2/common_file
example/dir1/common_dir/dir2/contents_differ
example/dir1/common_dir/dir2/dir_only_in_dir2
example/dir1/common_dir/dir2/file_in_dir1
example/dir1/common_dir/dir2/file_only_in_dir2
example/dir1/common_file
example/dir1/contents_differ
example/dir1/dir_only_in_dir1
example/dir1/file_in_dir1
example/dir1/file_only_in_dir1
example/dir2
example/dir2/common_dir
example/dir2/common_dir/dir1
example/dir2/common_dir/dir1/common_dir
example/dir2/common_dir/dir1/common_file
example/dir2/common_dir/dir1/contents_differ
example/dir2/common_dir/dir1/dir_only_in_dir1
example/dir2/common_dir/dir1/file_in_dir1
example/dir2/common_dir/dir1/file_only_in_dir1
example/dir2/common_dir/dir2
example/dir2/common_dir/dir2/common_dir
example/dir2/common_dir/dir2/common_file
example/dir2/common_dir/dir2/contents_differ
example/dir2/common_dir/dir2/dir_only_in_dir2
example/dir2/common_dir/dir2/file_in_dir1
example/dir2/common_dir/dir2/file_only_in_dir2
example/dir2/common_file
example/dir2/contents_differ
example/dir2/dir_only_in_dir2
example/dir2/file_in_dir1
example/dir2/file_only_in_dir2

Та же структура каталогов повторяется один раз в каталогах « common_dir », чтобы дать интересные варианты рекурсивного сравнения.

Сравнение файлов

cmp () сравнивает два файла в файловой системе.

filecmp_cmp.py

import filecmp

print('common_file    :', end' ')
print(filecmp.cmp('example/dir1/common_file',
                  'example/dir2/common_file',
                  shallowTrue),
      end' ')
print(filecmp.cmp('example/dir1/common_file',
                  'example/dir2/common_file',
                  shallowFalse))

print('contents_differ:', end' ')
print(filecmp.cmp('example/dir1/contents_differ',
                  'example/dir2/contents_differ',
                  shallowTrue),
      end' ')
print(filecmp.cmp('example/dir1/contents_differ',
                  'example/dir2/contents_differ',
                  shallowFalse))

print('identical      :', end' ')
print(filecmp.cmp('example/dir1/file_only_in_dir1',
                  'example/dir1/file_only_in_dir1',
                  shallowTrue),
      end' ')
print(filecmp.cmp('example/dir1/file_only_in_dir1',
                  'example/dir1/file_only_in_dir1',
                  shallowFalse))

Аргумент shallow сообщает cmp () , следует ли просматривать содержимое файла в дополнение к его метаданным. По умолчанию выполняется неглубокое сравнение с использованием информации, доступной из os.stat () . Если результаты статистики совпадают, файлы считаются одинаковыми. Поскольку вывод stat включает индексный дескриптор в Linux, отдельные файлы не считаются одинаковыми, даже если все их другие метаданные (размер, время создания и т. Д.) Совпадают. В этих случаях сравнивается содержимое файла. Если shallow имеет значение False , содержимое файла всегда сравнивается.

$ python3 filecmp_cmp.py

common_file    : True True
contents_differ: False False
identical      : True True

Чтобы сравнить набор файлов в двух каталогах без рекурсии, используйте cmpfiles () . Аргументы – это имена каталогов и список файлов, которые нужно проверить в двух местах. Передаваемый список общих файлов должен содержать только имена файлов (каталоги всегда приводят к несоответствию), и файлы должны присутствовать в обоих местах. В следующем примере показан простой способ создания общего списка. При сравнении также используется флаг shallow , как и в случае с cmp () .

filecmp_cmpfiles.py

import filecmp
import os

# Determine the items that exist in both directories
d1_contents  set(os.listdir('example/dir1'))
d2_contents  set(os.listdir('example/dir2'))
common  list(d1_contents & d2_contents)
common_files  [
    f
    for f in common
    if os.path.isfile(os.path.join('example/dir1', f))
]
print('Common files:', common_files)

# Compare the directories
match, mismatch, errors  filecmp.cmpfiles(
    'example/dir1',
    'example/dir2',
    common_files,
)
print('Match       :', match)
print('Mismatch    :', mismatch)
print('Errors      :', errors)

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

$ python3 filecmp_cmpfiles.py

Common files: ['contents_differ', 'file_in_dir1', 'common_file']
Match       : ['common_file']
Mismatch    : ['contents_differ', 'file_in_dir1']
Errors      : []

Сравнение каталогов

Описанные ранее функции подходят для относительно простых сравнений. Для рекурсивного сравнения больших деревьев каталогов или для более полного анализа более полезен класс dircmp . В простейшем случае report () печатает отчет, в котором сравниваются два каталога.

filecmp_dircmp_report.py

import filecmp

dc  filecmp.dircmp('example/dir1', 'example/dir2')
dc.report()

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

$ python3 filecmp_dircmp_report.py

diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file']
Differing files : ['contents_differ']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']

Для получения дополнительных сведений и рекурсивного сравнения используйте report_full_closure () :

filecmp_dircmp_report_full_closure.py

import filecmp

dc  filecmp.dircmp('example/dir1', 'example/dir2')
dc.report_full_closure()

Вывод включает сравнения всех параллельных подкаталогов.

$ python3 filecmp_dircmp_report_full_closure.py

diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file']
Differing files : ['contents_differ']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']

diff example/dir1/common_dir example/dir2/common_dir
Common subdirectories : ['dir1', 'dir2']

diff example/dir1/common_dir/dir1 example/dir2/common_dir/dir1
Identical files : ['common_file', 'contents_differ',
'file_in_dir1', 'file_only_in_dir1']
Common subdirectories : ['common_dir', 'dir_only_in_dir1']

diff example/dir1/common_dir/dir1/common_dir
example/dir2/common_dir/dir1/common_dir

diff example/dir1/common_dir/dir1/dir_only_in_dir1
example/dir2/common_dir/dir1/dir_only_in_dir1

diff example/dir1/common_dir/dir2 example/dir2/common_dir/dir2
Identical files : ['common_file', 'contents_differ',
'file_only_in_dir2']
Common subdirectories : ['common_dir', 'dir_only_in_dir2',
'file_in_dir1']

diff example/dir1/common_dir/dir2/common_dir
example/dir2/common_dir/dir2/common_dir

diff example/dir1/common_dir/dir2/dir_only_in_dir2
example/dir2/common_dir/dir2/dir_only_in_dir2

diff example/dir1/common_dir/dir2/file_in_dir1
example/dir2/common_dir/dir2/file_in_dir1

Использование различий в программе

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

filecmp_dircmp_list.py

import filecmp
import pprint

dc  filecmp.dircmp('example/dir1', 'example/dir2')
print('Left:')
pprint.pprint(dc.left_list)

print('\nRight:')
pprint.pprint(dc.right_list)

Файлы и подкаталоги, содержащиеся в сравниваемых каталогах, перечислены в left_list и right_list .

$ python3 filecmp_dircmp_list.py

Left:
['common_dir',
 'common_file',
 'contents_differ',
 'dir_only_in_dir1',
 'file_in_dir1',
 'file_only_in_dir1']

Right:
['common_dir',
 'common_file',
 'contents_differ',
 'dir_only_in_dir2',
 'file_in_dir1',
 'file_only_in_dir2']

Входные данные можно фильтровать, передавая конструктору список имен, которые следует игнорировать. По умолчанию имена RCS , CVS и теги игнорируются.

filecmp_dircmp_list_filter.py

import filecmp
import pprint

dc  filecmp.dircmp('example/dir1', 'example/dir2',
                    ignore['common_file'])

print('Left:')
pprint.pprint(dc.left_list)

print('\nRight:')
pprint.pprint(dc.right_list)

В этом случае « common_file » не входит в список файлов для сравнения.

$ python3 filecmp_dircmp_list_filter.py

Left:
['common_dir',
 'contents_differ',
 'dir_only_in_dir1',
 'file_in_dir1',
 'file_only_in_dir1']

Right:
['common_dir',
 'contents_differ',
 'dir_only_in_dir2',
 'file_in_dir1',
 'file_only_in_dir2']

Имена файлов, общие для обоих входных каталогов, сохраняются в common , а файлы, уникальные для каждого каталога, перечислены в left_only и right_only .

filecmp_dircmp_membership.py

import filecmp
import pprint

dc  filecmp.dircmp('example/dir1', 'example/dir2')
print('Common:')
pprint.pprint(dc.common)

print('\nLeft:')
pprint.pprint(dc.left_only)

print('\nRight:')
pprint.pprint(dc.right_only)

«Левый» каталог – это первый аргумент для dircmp () , а «правый» каталог – второй.

$ python3 filecmp_dircmp_membership.py

Common:
['common_dir', 'common_file', 'contents_differ', 'file_in_dir1']

Left:
['dir_only_in_dir1', 'file_only_in_dir1']

Right:
['dir_only_in_dir2', 'file_only_in_dir2']

Общие члены могут быть далее разбиты на файлы, каталоги и «забавные» элементы (все, что имеет другой тип в двух каталогах или где есть ошибка от os.stat () ).

filecmp_dircmp_common.py

import filecmp
import pprint

dc  filecmp.dircmp('example/dir1', 'example/dir2')
print('Common:')
pprint.pprint(dc.common)

print('\nDirectories:')
pprint.pprint(dc.common_dirs)

print('\nFiles:')
pprint.pprint(dc.common_files)

print('\nFunny:')
pprint.pprint(dc.common_funny)

В данных примера элемент с именем « file_in_dir1 » представляет собой файл в одном каталоге и подкаталог в другом, поэтому он отображается в списке смешных.

$ python3 filecmp_dircmp_common.py

Common:
['common_dir', 'common_file', 'contents_differ', 'file_in_dir1']

Directories:
['common_dir']

Files:
['common_file', 'contents_differ']

Funny:
['file_in_dir1']

Аналогичным образом разбираются различия между файлами.

filecmp_dircmp_diff.py

import filecmp

dc  filecmp.dircmp('example/dir1', 'example/dir2')
print('Same      :', dc.same_files)
print('Different :', dc.diff_files)
print('Funny     :', dc.funny_files)

Файл not_the_same сравнивается только с помощью os.stat () , а его содержимое не проверяется, поэтому он включается в список same_files .

$ python3 filecmp_dircmp_diff.py

Same      : ['common_file']
Different : ['contents_differ']
Funny     : []

Наконец, подкаталоги также сохраняются для упрощения рекурсивного сравнения.

filecmp_dircmp_subdirs.py

import filecmp

dc  filecmp.dircmp('example/dir1', 'example/dir2')
print('Subdirectories:')
print(dc.subdirs)

Атрибут subdirs – это словарь, сопоставляющий имя каталога с новыми объектами dircmp .

$ python3 filecmp_dircmp_subdirs.py

Subdirectories:
{'common_dir': }

Смотрите также