Найшвидший спосіб визначити, чи є два файли однакового вмісту в Unix / Linux?


231

У мене є скрипт оболонки, в якому мені потрібно перевірити, чи містять два файли однакові дані чи ні. Я роблю це для багатьох файлів, і в моєму сценарії diffкоманда, здається, є вузьким місцем продуктивності.

Ось рядок:

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

if ($status) then ...

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


10
Це насправді, але ви не запитуєте, чи два файли однакові, ви запитуєте, чи мають два файли однаковий вміст. Одні файли мають однакові вставки (і той самий пристрій).
Зано

1
На відміну від прийнятої відповіді, вимірювання у цій відповіді не визнає жодної помітної різниці між diffта cmp.
wedi

Відповіді:


388

Я вірю cmp, що зупиниться на першій байтній різниці:

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

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

9
cmp -s $old $newтакож працює. -sскорочено--silent
Ромер

7
Щоб збільшити швидкість, перед порівнянням вмісту слід перевірити, чи розміри файлів рівні. Хтось знає, чи cmp це робить?
BeowulfNode42

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

6
@ BeowulfNode42 так, будь-яка гідна реалізація cmpспочатку перевірить розмір файлу. Ось версія GNU, якщо ви хочете побачити додаткові оптимізації, які вона включає: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham

53

Мені подобається @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 Це тому, що алгоритми хешування не один до одного. Вони розроблені таким чином, що хеширующий простір великий, а різні входи мають високий шанс створити різні хеші. Однак реальність полягає в тому, що хеш-простір обмежений, тоді як діапазон можливих файлів до хешу - ні, зрештою у вас виникне зіткнення. У криптології це називається Attack Birthday Attack .
буде

5
@will Eh, це ефективно гарантовано працює. Шанси на це не працюють, математично кажучи, навколо 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"оскільки ви можете використовувати команду в вираженні безпосередньо. Це підміняє $?. У результаті буде встановлено порівняння існуючого стану команди. І це робить інша відповідь. btw. Якщо хтось бореться --silent, це не підтримується скрізь (busbox). використання-s
папо

4

Для файлів, які не відрізняються, будь-який метод потребує повного читання обох файлів, навіть якщо прочитане було раніше.

Альтернативи немає. Тому для створення хешів або контрольних сум у певний момент часу потрібно прочитати весь файл. Великі файли потребують часу.

Пошук метаданих файлів набагато швидше, ніж читання великого файлу.

Отже, чи є метадані файлів, які ви можете використовувати, щоб встановити, що файли різні? Розмір файлу ? або навіть результати файлової команди, яка просто читає невелику частину файлу?

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

  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 видасть кількість байтів файлу. Див. "Людина 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, щоб робити речі між двома файлами ....

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.