Самый быстрый способ определить, имеют ли два файла одинаковое содержимое в Unix / Linux?


233

У меня есть сценарий оболочки, в котором мне нужно проверить, содержат ли два файла одинаковые данные или нет. Я делаю это для большого количества файлов, и в моем скрипте diffкоманда кажется узким местом производительности.

Вот строка:

diff -q $dst $new > /dev/null

if ($status) then ...

Может ли быть более быстрый способ сравнения файлов, возможно, собственный алгоритм вместо стандартного diff?


10
Это действительно придирчиво, но вы не спрашиваете, являются ли два файла одинаковыми, вы спрашиваете, имеют ли два файла одинаковое содержание. Одинаковые файлы имеют одинаковые inode (и одно и то же устройство).
Зано

1
В отличие от принятого ответа, измерение в этом ответе не распознает какой-либо заметной разницы между diffи cmp.
Веди

Ответы:


391

Я считаю, cmpчто остановится на первой разнице байтов:

cmp --silent $old $new || echo "files are different"

1
Как я могу добавить больше команд, чем только одну? Я хочу скопировать файл и перезагрузиться.
feedc0de

9
cmp -s $old $newтоже работает. -sкоротка для--silent
Rohmer

7
В качестве повышения скорости, вы должны проверить, равны ли размеры файлов перед сравнением содержимого. Кто-нибудь знает, делает ли это cmp?
BeowulfNode42

3
Для запуска нескольких команд вы можете использовать скобки: cmp -s old new || {эхо нет; эхо; эхо же; }
unfa

6
@ BeowulfNode42 да, любая приличная реализация cmpсначала проверит размер файла. Вот версия GNU, если вы хотите увидеть дополнительные оптимизации, которые она включает: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Райан Грэм,

54

Мне нравится @Alex Howansky для этого использовал cmp --silent. Но мне нужен как положительный, так и отрицательный ответ, поэтому я использую:

cmp --silent file1 file2 && echo '### SUCCESS: Files Are Identical! ###' || echo '### WARNING: Files Are Different! ###'

Затем я могу запустить это в терминале или с помощью ssh для проверки файлов на постоянный файл.


16
Если ваша echo successкоманда (или любая другая команда, которую вы положили на ее место) терпит неудачу, будет запущена ваша команда «отрицательный ответ». Вы должны использовать конструкцию if-then-else-fi. Например, как этот простой пример .
Wildcard

18

Почему вы не получаете хэш содержимого обоих файлов?

Попробуйте этот скрипт, назовите его, например, script.sh, а затем запустите его следующим образом: script.sh file1.txt file2.txt

#!/bin/bash

file1=`md5 $1`
file2=`md5 $2`

if [ "$file1" = "$file2" ]
then
    echo "Files have the same content"
else
    echo "Files have NOT the same content"
fi

2
@THISUSERNEEDSHELP Это потому, что алгоритмы хеширования не один в один. Они спроектированы таким образом, что пространство хеширования велико, и разные входы имеют высокую вероятность создания разных хешей. Однако реальность такова, что хеш-пространство конечно, а диапазон возможных файлов для хеширования - нет, в конечном итоге вы столкнетесь. В криптологии это называется Атака на День Рождения .
будет

5
@ Хорошо, это эффективно работает. С математической точки зрения, шансы на то, что он не работает, примерно одинаковы 1/(2^511). Если вы не беспокоитесь о том, что кто-то намеренно пытается создать столкновение, идея этого метода, создающего ложное срабатывание, на самом деле не является серьезной проблемой. cmpвсе еще более эффективен, так как он не должен читать весь файл в случае, если файлы не совпадают.
Ajedi32

12
OP запросил самый быстрый способ ... не будет ли поиск первого несоответствующего бита (с использованием cmp) быстрее (если они не совпадают), чем хеширование всего файла, особенно если файлы большие?
KoZm0kNoT

3
MD5 лучше, если вы делаете сравнение один ко многим. Вы можете хранить хэш md5 как атрибут или в базе данных для каждого файла. Если появляется новый файл, и вам нужно проверить, существует ли такой же файл где-либо в файловой системе, все, что вам нужно сделать, это вычислить хэш нового файла и проверить все предыдущие. Я уверен, что Git использует хеширование для проверки изменений файла во время коммита, но они используют SHA1.
JimHough

3
@ BeowulfNode42 Вот почему я предвосхитил свой комментарий: «Если вы не беспокоитесь о том, что кто-то намеренно пытается создать столкновение»
Ajedi32

5

Поскольку я отстой и у меня недостаточно очков репутации, я не могу добавить этот комментарий в качестве комментария.

Но, если вы собираетесь использовать cmpкоманду (и не хотите / не хотите быть многословным), вы можете просто получить статус выхода. По cmpсправочной странице:

Если ФАЙЛ '-' или отсутствует, прочитайте стандартный ввод. Состояние выхода равно 0, если входы одинаковы, 1, если отличается, 2, если проблема.

Итак, вы можете сделать что-то вроде:

STATUS="$(cmp --silent $FILE1 $FILE2; echo $?)"  # "$?" gives exit status for each comparison

if [[$STATUS -ne 0]]; then  # if status isn't equal to 0, then execute code
    DO A COMMAND ON $FILE1
else
    DO SOMETHING ELSE
fi

да, но на самом деле это более сложный способ выполнения, cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fiкоторый, в свою очередь, является более сложным, cmp --silent $FILE1 $FILE2 || echo "files differ"поскольку вы можете напрямую использовать команду в выражении. Это заменяет $?. В результате сравнивается существующий статус команды. И это то, что делает другой ответ. Кстати. Если с кем-то борются --silent, это не везде поддерживается (busybox). использовать-s
папа

4

Для файлов, которые не отличаются, любой метод потребует полностью прочитать оба файла, даже если чтение было в прошлом.

Альтернативы нет. Поэтому создание хэшей или контрольных сумм в определенный момент времени требует чтения всего файла. Большие файлы занимают время.

Извлечение метаданных файла происходит намного быстрее, чем чтение большого файла.

Итак, есть ли какие-нибудь метаданные файла, которые вы можете использовать, чтобы установить, что файлы разные? Размер файла ? или даже результаты команды file, которая просто читает небольшую часть файла?

Фрагмент кода примера размера файла:

  ls -l $1 $2 | 
  awk 'NR==1{a=$5} NR==2{b=$5} 
       END{val=(a==b)?0 :1; exit( val) }'

[ $? -eq 0 ] && echo 'same' || echo 'different'  

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


1
Используйте, ls -nчтобы избежать проблем, если имена пользователей или групп имеют пробелы.
Трикасс

2

Попробуйте также использовать команду cksum:

chk1=`cksum <file1> | awk -F" " '{print $1}'`
chk2=`cksum <file2> | awk -F" " '{print $1}'`

if [ $chk1 -eq $chk2 ]
then
  echo "File is identical"
else
  echo "File is not identical"
fi

Команда cksum выведет количество байтов файла. Смотрите "man cksum".


2
Это была моя первая мысль тоже. Однако хэши имеют смысл, если вам приходится сравнивать один и тот же файл много раз, поскольку хеш вычисляется только один раз. Если вы сравниваете его только один раз, то md5все равно считывает весь файл, поэтому cmpостановка на первом разнице будет намного быстрее.
Франческо Донди

0

Проведя некоторое тестирование с Raspberry Pi 3B + (я использую файловую систему с наложением, и мне нужно периодически выполнять синхронизацию), я провел собственное сравнение для diff -q и cmp -s; обратите внимание, что это журнал из / dev / shm, поэтому скорость доступа к диску не проблема:

[root@mypi shm]# dd if=/dev/urandom of=test.file bs=1M count=100 ; time diff -q test.file test.copy && echo diff true || echo diff false ; time cmp -s test.file test.copy && echo cmp true || echo cmp false ; cp -a test.file test.copy ; time diff -q test.file test.copy && echo diff true || echo diff false; time cmp -s test.file test.copy && echo cmp true || echo cmp false
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 6.2564 s, 16.8 MB/s
Files test.file and test.copy differ

real    0m0.008s
user    0m0.008s
sys     0m0.000s
diff false

real    0m0.009s
user    0m0.007s
sys     0m0.001s
cmp false
cp: overwrite âtest.copyâ? y

real    0m0.966s
user    0m0.447s
sys     0m0.518s
diff true

real    0m0.785s
user    0m0.211s
sys     0m0.573s
cmp true
[root@mypi shm]# pico /root/rwbscripts/utils/squish.sh

Я запускал его пару раз. У cmp -s было несколько меньше времени на тестовом боксе, который я использовал. Так что, если вы хотите использовать cmp -s для работы между двумя файлами ....

identical (){
  echo "$1" and "$2" are the same.
  echo This is a function, you can put whatever you want in here.
}
different () {
  echo "$1" and "$2" are different.
  echo This is a function, you can put whatever you want in here, too.
}
cmp -s "$FILEA" "$FILEB" && identical "$FILEA" "$FILEB" || different "$FILEA" "$FILEB"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.