Najszybszy sposób na sprawdzenie, czy dwa pliki mają tę samą zawartość w systemach Unix / Linux?

231

Mam skrypt powłoki, w którym muszę sprawdzić, czy dwa pliki zawierają te same dane, czy nie. Robię to dla wielu plików, aw moim skrypcie diffpolecenie wydaje się być wąskim gardłem wydajności.

Oto linia:

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

if ($status) then ...

Czy może istnieć szybszy sposób porównywania plików, może niestandardowy algorytm zamiast domyślnego diff?

JDS
źródło
10
To naprawdę dziwaczne, ale nie pytasz, czy dwa pliki są takie same, pytasz, czy dwa pliki mają identyczną zawartość. Te same pliki mają identyczne i-węzły (i to samo urządzenie).
Zano
1
W przeciwieństwie do przyjętej odpowiedzi, pomiar w tej odpowiedzi nie rozpoznaje żadnej zauważalnej różnicy między diffi cmp.
śr

Odpowiedzi:

388

Wierzę, cmpże zatrzyma się przy pierwszej bajcie różnicy:

cmp --silent $old $new || echo "files are different"
Alex Howansky
źródło
1
Jak mogę dodać więcej poleceń niż tylko jedno? Chcę skopiować plik i uruchomić.
feedc0de
9
cmp -s $old $newdziała również. -sjest skrótem od--silent
Rohmer
7
Aby przyspieszyć, przed porównaniem zawartości należy sprawdzić, czy rozmiary plików są równe. Czy ktoś wie, czy robi to cmp?
BeowulfNode42
3
Aby uruchomić wiele poleceń, możesz użyć nawiasów: cmp -s stary nowy || {echo nie; echo; echo to samo; }
unfa
6
@ BeowulfNode42 tak, każda przyzwoita implementacja cmpsprawdzi najpierw rozmiar pliku. Oto wersja GNU, jeśli chcesz zobaczyć dodatkowe optymalizacje, które zawiera: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham
53

Lubię @Alex Howansky użył do tego „cmp --silent”. Ale potrzebuję zarówno pozytywnej, jak i negatywnej odpowiedzi, więc używam:

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

Następnie mogę uruchomić to w terminalu lub za pomocą ssh, aby sprawdzić pliki względem stałego pliku.

pn1 koleś
źródło
16
Jeśli twoje echo successpolecenie (lub cokolwiek innego, które umieścisz na jego miejscu) nie powiedzie się, zostanie wykonane polecenie „odpowiedź negatywna”. Powinieneś użyć konstrukcji „jeśli-to-inaczej-fi”. Na przykład jak ten prosty przykład .
Wildcard
18

Dlaczego nie masz skrótu zawartości obu plików?

Wypróbuj ten skrypt, wywołaj go na przykład script.sh, a następnie uruchom w następujący sposób: script.sh plik1.txt plik2.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
jabaldonedo
źródło
2
@THISUSERNEEDSHELP To dlatego, że algorytmy mieszające nie są jeden do jednego. Są zaprojektowane tak, że przestrzeń skrótów jest duża, a różne dane wejściowe mają dużą szansę na wygenerowanie różnych skrótów. Rzeczywistość jest jednak taka, że ​​przestrzeń mieszania jest skończona, podczas gdy zakres możliwych plików do skrótu nie jest - w końcu będziesz mieć kolizję. W kryptologii nazywa się to atakiem urodzinowym .
będzie
5
@Will Eh, to gwarantuje, że działa skutecznie . Szanse na to, że nie zadziała, są matematyczne 1/(2^511). O ile nie martwisz się, że ktoś celowo spróbuje stworzyć kolizję, pomysł tej metody wywołującej fałszywy alarm nie jest poważnym problemem. cmpjest jednak jeszcze bardziej wydajny, ponieważ nie musi czytać całego pliku w przypadku, gdy pliki się nie zgadzają.
Ajedi32
12
OP poprosił o NAJSZYBSZY sposób ... czy wyszukiwanie pierwszego niepasującego bitu (przy użyciu cmp) nie byłoby szybsze (jeśli nie pasują) niż mieszanie całego pliku, zwłaszcza jeśli pliki są duże?
KoZm0kNoT
3
md5 jest najlepszy, jeśli porównujesz jeden do wielu. Możesz przechowywać skrót md5 jako atrybut lub w bazie danych dla każdego pliku. Jeśli pojawi się nowy plik i musisz sprawdzić, czy ten sam plik istnieje w dowolnym miejscu w systemie plików, wystarczy obliczyć skrót nowego pliku i porównać z wszystkimi poprzednimi. Jestem pewien, że Git używa mieszania do sprawdzania zmian plików podczas zatwierdzania, ale używają SHA1.
JimHough,
3
@ BeowulfNode42 Dlatego poprzedziłem swój komentarz słowem „Chyba że martwisz się, że ktoś celowo spróbuje stworzyć kolizję”
Ajedi32
5

Ponieważ ssę i nie mam wystarczającej liczby punktów reputacji, nie mogę dodać tego smakołyka jako komentarza.

Ale jeśli zamierzasz użyć cmppolecenia (i nie potrzebujesz / nie chcesz być gadatliwy), możesz po prostu pobrać status wyjścia. Na cmpstronę podręcznika :

Jeśli PLIK jest „-” lub go brakuje, przeczytaj standardowe wejście. Status wyjścia wynosi 0, jeśli wejścia są takie same, 1 jeśli są różne, 2 jeśli występują problemy.

Więc możesz zrobić coś takiego:

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
Gregory Martin
źródło
tak, ale w rzeczywistości jest to bardziej skomplikowany sposób, cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fiktóry z kolei jest bardziej skomplikowany, cmp --silent $FILE1 $FILE2 || echo "files differ"ponieważ można bezpośrednio użyć polecenia w wyrażeniu. Zastępuje $?. W rezultacie status polecenia zostanie porównany. I to właśnie robi druga odpowiedź. btw. Jeśli ktoś ma --silentproblem, nie jest obsługiwany wszędzie (busybox). use-s
papo
4

W przypadku plików, które nie są różne, każda metoda będzie wymagać całkowitego odczytania obu plików, nawet jeśli odczyt był w przeszłości.

Nie ma alternatywy. Tak więc tworzenie skrótów lub sum kontrolnych w pewnym momencie wymaga odczytania całego pliku. Duże pliki wymagają czasu.

Pobieranie metadanych pliku jest znacznie szybsze niż czytanie dużego pliku.

Czy istnieją jakieś metadane plików, których można użyć do ustalenia, że ​​pliki są różne? Rozmiar pliku ? a nawet wyniki polecenia file, które odczytuje tylko niewielką część pliku?

Fragment kodu przykładowego rozmiaru pliku:

  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'  

Jeśli pliki są tego samego rozmiaru, oznacza to, że nie możesz odczytać pełnych plików.

Jim Mcnamara
źródło
1
Użyj, ls -naby uniknąć problemów, jeśli nazwy użytkowników lub grup mają spacje.
tricasse
2

Spróbuj także użyć polecenia 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

Polecenie cksum wyświetli liczbę bajtów pliku. Zobacz „man cksum”.

Nono Taps
źródło
2
To była moja pierwsza myśl. Jednak skróty mają sens, jeśli trzeba wiele razy porównywać ten sam plik, ponieważ skrót jest obliczany tylko raz. Jeśli porównujesz go tylko raz, to i tak md5czyta cały plik, więc cmpzatrzymanie się przy pierwszej różnicy będzie znacznie szybsze.
Francesco Dondi,
0

Przeprowadzając testy z Raspberry Pi 3B + (używam nakładkowego systemu plików i muszę okresowo synchronizować), uruchomiłem własne porównanie dla diff -q i cmp -s; zwróć uwagę, że jest to dziennik z wnętrza / dev / shm, więc prędkości dostępu do dysku nie stanowią problemu:

[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

Uruchomiłem to kilka razy. cmp -s konsekwentnie miał nieco krótsze czasy na polu testowym, którego używałem. Więc jeśli chcesz używać cmp -s do robienia rzeczy między dwoma plikami ...

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"
Jack Simth
źródło