Есть ли безопасный способ запустить сравнение двух сжатых файлов в формате zip?

Кажется, это не будет детерминированной вещью, или есть способ сделать это надежно?

Ответов (13)

Я нашел облегчение в этом простом скрипте Perl: diffzips.pl

Он рекурсивно сравнивает каждый zip-файл внутри исходного zip, что особенно полезно для разных форматов пакетов Java: jar, war и ear.

zipcmp использует более простой подход и не рекурсивно превращается в архивные zip-файлы .

На самом деле gzip и bzip2 поставляются со специальными инструментами для этого.

С помощью gzip:

$ zdiff file1.gz file2.gz

С bzip2:

$ bzdiff file1.bz2 file2.bz2

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

Я обычно использую подход, подобный @ mrabbit, но запускаю 2 команды распаковки и при необходимости различаю вывод. Например, мне нужно сравнить 2 файла WAR Java.

$ sdiff --width 160 \
   <(unzip -l -v my_num1.war | cut -c 1-9,59-,49-57 | sort -k3) \
   <(unzip -l -v my_num2.war | cut -c 1-9,59-,49-57 | sort -k3)

Результат такой:

--------          -------                                                       --------          -------
Archive:                                                                        Archive:
-------- -------- ----                                                          -------- -------- ----
48619281          130 files                                                   | 51043693          130 files
    1116 060ccc56 index.jsp                                                         1116 060ccc56 index.jsp
       0 00000000 META-INF/                                                            0 00000000 META-INF/
     155 b50f41aa META-INF/MANIFEST.MF                                        |      155 701f1623 META-INF/MANIFEST.MF
 Length   CRC-32  Name                                                           Length   CRC-32  Name
    1179 b42096f1 version.jsp                                                       1179 b42096f1 version.jsp
       0 00000000 WEB-INF/                                                             0 00000000 WEB-INF/
       0 00000000 WEB-INF/classes/                                                     0 00000000 WEB-INF/classes/
       0 00000000 WEB-INF/classes/com/                                                 0 00000000 WEB-INF/classes/com/
...
...

Я отказался от попыток использовать существующие инструменты и написал небольшой скрипт на bash, который мне подходит:

#!/bin/bash
# Author: Onno Benschop, [email protected]
# Note: This requires enough space for both archives to be extracted in the tempdir

if [ $# -ne 2 ] ; then
  echo Usage: $(basename "$0") zip1 zip2
  exit
fi

# Make temporary directories
archive_1=$(mktemp -d)
archive_2=$(mktemp -d)

# Unzip the archives
unzip -qqd"${archive_1}" "$1"
unzip -qqd"${archive_2}" "$2"

# Compare them
diff -r "${archive_1}" "${archive_2}"

# Remove the temporary directories
rm -rf "${archive_1}" "${archive_2}"

zipcmp сравнивает zip-архивы zip1 и zip2 и проверяет, содержат ли они одинаковые файлы, сравнивая их имена, размеры без сжатия и CRC. Различия в порядке файлов и размерах сжатых файлов игнорируются.

sudo apt-get install zipcmp

Решение на Python для zip-файлов:

import difflib
import zipfile

def diff(filename1, filename2):
    differs = False

    z1 = zipfile.ZipFile(open(filename1))
    z2 = zipfile.ZipFile(open(filename2))
    if len(z1.infolist()) != len(z2.infolist()):
        print "number of archive elements differ: {} in {} vs {} in {}".format(
            len(z1.infolist()), z1.filename, len(z2.infolist()), z2.filename)
        return 1
    for zipentry in z1.infolist():
        if zipentry.filename not in z2.namelist():
            print "no file named {} found in {}".format(zipentry.filename,
                                                        z2.filename)
            differs = True
        else:
            diff = difflib.ndiff(z1.open(zipentry.filename),
                                 z2.open(zipentry.filename))
            delta = ''.join(x[2:] for x in diff
                            if x.startswith('- ') or x.startswith('+ '))
            if delta:
                differs = True
                print "content for {} differs:\n{}".format(
                    zipentry.filename, delta)
    if not differs:
        print "all files are the same"
        return 0
    return 1

Использовать как

diff(filename1, filename2)

Он построчно сравнивает файлы в памяти и показывает изменения.

Многие решения здесь либо просто проверяют CRC, чтобы увидеть, существуют ли различия , являются ли сложными сценариями, требуют распаковки на диск, используют внешние программы или нуждаются в определенных форматах сжатия, отличных от того, о котором вы спрашивали ( zcat НЕ работает с zip ).

Вот простой, легкий для чтения и должен работать везде, где у вас есть bash, который показывает различия между содержимым файла, если, как и я, это то, что вам нужно, когда вы столкнулись с этим вопросом :

diff \
    <(zipinfo -1 "$zip1" '*' \
    | grep '[^/]$' \
    | sort \
    | while IFS= read -r file; do unzip -c "$zip1" "$file"; done \
    ) \
    <(zipinfo -1 "$zip2" '*' \
    | grep '[^/]$' \
    | sort \
    | while IFS= read -r file; do unzip -c "$zip2" "$file"; done \
    )

Это распаковывает в памяти, а не на диск, высвобождая данные из канала по мере их разницы (он не будет распаковывать, а затем сравнивать, поэтому не должен использовать много памяти).
Хотите изменить параметры различий для игнорирования пробелов или использования бок о бок? Измените diff на diff -w или gvimdiff (это сохранит все файлы в памяти) и так далее.
Скажете, вы хотите только сравнить .js файлы? Измените * на *.js .
Хотите видеть только те имена файлов, которые отсутствуют в одном или другом? Удалите while линию, и она не будет распаковывать.

Легкий.

Он даже будет безопасно обрабатывать (пропускать и записывать stderr ) имена файлов с «недопустимыми» символами, такими как перевод строки и обратная косая черта.
Нет "безопасного" р, чем это.

Ответ slm довольно хорош для возврата файлов, которые отличаются (без отображения различий) и даже не распаковываются, что приятно. Если по какой-то причине вы хотите этого, но на шаг выше CRC, в этом ответе вы можете добавить, например, | sha512sum перед, ; done и получить `` худшее из обоих миров '': P


Точно так же сравнительно легко сравнить архив и реальный каталог:

diff \
    <(zipinfo -1 "$zip" '*' \
    | grep '[^/]$' \
    | sort \
    | while IFS= read -r file; do unzip -c "$zip" "$file"; done \
    ) \
    <(find "$directory" -type f -name '*' \
    | sort \
    | while IFS= read -r file
      do
          printf 'Archive:  %s\n  inflating: %s\n' "$directory" `echo $file | sed "s|$directory/||"`
          cat "$file"
          echo
      done \
    )

Или, игнорируя файлы только в каталоге, в основном удобный пробный прогон unzip -o -d "$directory" :

diff \
    <(zipinfo -1 "$zip" '*' \
    | grep '[^/]$' \
    | sort \
    | while IFS= read -r file; do unzip -c "$zip" "$file"; done \
    ) \
    <(zipinfo -1 "$zip" '*' \
    | grep '[^/]$' \
    | sort \
    | while IFS= read -r file
      do
          printf 'Archive:  %s\n  inflating: %s\n' "$directory" "$file"
          cat "$directory/$file"
          echo
      done \
    )

Windows? Извините. Хотя сценарии просты и их будет легко перенести на [синтаксически] фантастическую оболочку PowerShell, это не сработает. Собственный командлет только извлекает данные на диск, а MS до сих пор не исправила сломанный конвейер двоичных данных в PS, поэтому вы также не можете «безопасно» использовать внешние данные zip.exe таким образом.

Очевидно, что другие делали аналогичные вещи, используя .NET API напрямую , но он стал бы менее элегантным портом и больше переопределением в .NET: |


Замечание о «незаконных именах файлов», упомянутых ранее:
если вы хотите, чтобы он работал с ними, на самом деле это не так уж сложно; вам просто нужно поменять местами $fileс $(echo "$file" | sed 's/\\/\\\\/g;s/\^J/\n/g;s/\^M/\r/g').

Добавляйте другие символы ctrl по мере их появления.

Причина в том, что по какой-то причине, даже если zipinfoотображается имя файла, содержащее \nв нем как ^J, он не будет принимать эти безопасные имена unzipтолько для оригинала! И даже несмотря на то, что он МОЖЕТ извлекать эти незаконные имена файлов unzip -^, нет никакого способа получить эти исходные имена файлов zipinfoвообще. Таким образом, вам нужно создать исходное недопустимое имя файла из безопасного, непригодного для использования, чтобы ссылаться на них для сравнения :(
Если вы это сделаете, обратите внимание, что нет способа отличить ^Jбуквально от \nотображения как ^J, и этот zip не поддерживает /или ^@вообще внутри имен файлов.


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

(zipinfo -1 "$zip1"; zipinfo -1 "$zip2") \
    | grep '[^/]$' \
    | sort \
    | uniq \
    | while IFS= read -r file; do
        (diff <(unzip -p "$zip1" "$file") <(unzip -p "$zip2" "$file") | zip 'diff.zip' - \
        && zipinfo -s 'diff.zip' - | awk '{ print $4; }' | grep '[^0]' \
        && printf "@ -\[email protected]=$file\n" | zipnote -w 'diff.zip' \
        || zip -d 'diff.zip' -
        ) >/dev/null
      done

Не такой красивый сценарий, но теперь вы можете открыть его в выбранном вами архиваторе графического интерфейса или сделать, unzip -p diff.zip some/dir/some.file чтобы увидеть различия конкретно с этим файлом, или получить ответ «не найден», если нет различий, что на практике намного красивее. .

Если вы используете gzip, вы можете сделать что-то вроде этого:

# diff <(zcat file1.gz) <(zcat file2.gz)

WinMerge (только для Windows) имеет множество функций, одна из которых:

  • Поддержка архивных файлов с помощью 7-Zip

Надежно: разархивируйте оба, разн.

Понятия не имею, подходит ли вам этот ответ, но он работает.

У Beyond compare нет проблем с этим.

В общем, вы не можете избежать распаковки и последующего сравнения. Различные компрессоры будут приводить к разным байтовым потокам DEFLATEd, которые, когда INFLATEd приводят к одному и тому же исходному тексту. Вы не можете просто сравнить данные DEFLATEd друг с другом. В некоторых случаях это НЕ ИСПОЛЬЗУЕТСЯ.

Но в сценарии ZIP для каждой записи вычисляется и сохраняется CRC32. Поэтому, если вы хотите проверить файлы, вы можете просто сравнить сохраненный CRC32, связанный с каждым потоком DEFLATEd, с предостережениями в отношении свойств уникальности хэша CRC32. Возможно, вам понадобится сравнить FileName и CRC.

Вам понадобится ZIP-библиотека, которая читает zip-файлы и предоставляет эти вещи как свойства объекта «ZipEntry». DotNetZip сделает это для приложений .NET.

Это не особенно элегантно, но вы можете использовать приложение FileMerge, которое поставляется с инструментами разработчика Mac OS X, для сравнения содержимого zip-файлов с помощью настраиваемого фильтра.

Создайте скрипт ~/bin/zip_filemerge_filter.bash с содержимым:

#!/bin/bash
##
#  List the size, CR-32 checksum, and file path of each file in a zip archive,
#  sorted in order by file path.
##
unzip -v -l "${1}" | cut -c 1-9,59-,49-57 | sort -k3
exit $?

Сделайте скрипт исполняемым ( chmod +x ~/bin/zip_filemerge_filter.bash ).

Откройте FileMerge, откройте «Настройки» и перейдите на вкладку «Фильтры». Добавьте элемент в список с помощью: Extension: "zip", Filter: "~ / bin / zip_filemerge_filter.bash $ (FILE)", Display: Filtered, Apply *: No. (Я также добавил фильтр для .jar и файлы .war.)

Затем используйте FileMerge (или оболочку командной строки opendiff) для сравнения двух файлов .zip.

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