Jak porównać dwa pliki


Odpowiedzi:


92

Sprawdź diffpolecenie. To dobre narzędzie i możesz o tym przeczytać, pisząc man diffna terminalu.

Polecenie, które chcesz wykonać, diff File_1.txt File_2.txtspowoduje wyświetlenie różnicy między nimi i powinno wyglądać mniej więcej tak:

wprowadź opis zdjęcia tutaj

Szybka uwaga na temat odczytu wyniku trzeciego polecenia: „Strzałki” ( <i >) odnoszą się do wartości wiersza w lewym pliku ( <) w porównaniu do prawego pliku ( >), przy czym lewy plik to ten, który wprowadziłeś najpierw w wierszu poleceń, w tym przypadkuFile_1.txt

Dodatkowo można zauważyć 4. Polecenie to jest diff ... | tee Output_Fileto rury wyniki uzyskane diffw tee, który wykłada, że wyjście do pliku, tak aby można go zapisać na później, jeśli nie chcemy, aby to wszystko zobaczyć po prawej stronie konsoli tego drugiego.


Czy można to zrobić z innymi plikami (takimi jak obrazy)? A może ogranicza się tylko do dokumentów?
Gregory Opera

2
O ile mi wiadomo, ogranicza się do plików tekstowych. Kod będzie działał, ponieważ zasadniczo jest tekstem, ale wszelkie pliki binarne (które są obrazkami) po prostu znikną. Można porównać, aby zobaczyć, czy są one identyczne, wykonując: diff file1 file2 -s. Oto przykład: imgur.com/ShrQx9x
Mitch

Czy istnieje sposób pokolorowania wydruku? Chciałbym zachować to tylko CLI, ale z pewnym ... ludzkim dotykiem.
Lazar Ljubenović

36

Lub możesz użyć Meld Diff

Meld pomaga porównywać pliki, katalogi i projekty z kontrolą wersji. Zapewnia dwukierunkowe i trzykrotne porównanie plików i katalogów oraz obsługuje wiele popularnych systemów kontroli wersji.

Zainstaluj, uruchamiając:

sudo apt-get install meld

Twój przykład:

wprowadź opis zdjęcia tutaj

Porównaj katalog:

wprowadź opis zdjęcia tutaj

Przykład z pełnym tekstem:

wprowadź opis zdjęcia tutaj



13

FWIW, raczej podoba mi się to, co otrzymuję z wyjściowym wyjściem z diff

diff -y -W 120 File_1.txt File_2.txt

dałby coś takiego:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG

10

Możesz użyć polecenia cmp:

cmp -b "File_1.txt" "File_2.txt"

wyjście byłoby

a b differ: byte 25, line 3 is 125 U 116 N

cmpjest znacznie szybszy niż diffgdyby wszystko, czego chcesz, to kod powrotu.
stevesliva

8

Meldto naprawdę świetne narzędzie. Ale możesz także użyć diffusedo wizualnego porównania dwóch plików:

diffuse file1.txt file2.txt

wprowadź opis zdjęcia tutaj


7

Przyklejając się do pytania (plik1, plik2, plik wyjściowy z komunikatem „zmieniłem”) poniższy skrypt działa.

Skopiuj skrypt do pustego pliku, zapisz go jako compare.py, wykonaj go, uruchom za pomocą polecenia:

/path/to/compare.py <file1> <file2> <outputfile>

Scenariusz:

#!/usr/bin/env python

import sys
file1 = sys.argv[1]; file2 = sys.argv[2]; outfile = sys.argv[3]

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

with open(outfile, "wt") as out:
    for line in mismatch:
        out.write(line+" has changed"+"\n")

Za pomocą kilku dodatkowych wierszy możesz wydrukować plik wyjściowy lub terminal, w zależności od tego, czy plik wyjściowy jest zdefiniowany:

Aby wydrukować do pliku:

/path/to/compare.py <file1> <file2> <outputfile>

Aby wydrukować do okna terminala:

/path/to/compare.py <file1> <file2> 

Scenariusz:

#!/usr/bin/env python

import sys

file1 = sys.argv[1]; file2 = sys.argv[2]
try:
    outfile = sys.argv[3]
except IndexError:
    outfile = None

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

if outfile != None:
        with open(outfile, "wt") as out:
            for line in mismatch:
                out.write(line+" has changed"+"\n")
else:
    for line in mismatch:
        print line+" has changed"

4

Prostym sposobem jest użycie colordiff, które zachowuje się jak, diffale koloruje jego wynik. Jest to bardzo pomocne przy czytaniu różnic. Korzystając z twojego przykładu,

$ colordiff -u File_1.txt File_2.txt
--- File_1.txt  2016-12-24 17:59:17.409490554 -0500
+++ File_2.txt  2016-12-24 18:00:06.666719659 -0500
@@ -1,3 +1,3 @@
 User1 US
 User2 US
-User3 US
+User3 NG

gdzie uopcja daje zunifikowaną różnicę. Tak wygląda pokolorowany plik różnicowy:

wprowadź opis zdjęcia tutaj

Zainstaluj colordiff, uruchamiając sudo apt-get install colordiff.


1
Jeśli chcesz kolorów, uważam, że diff wbudowany w vim jest naprawdę łatwy w użyciu, jak w odpowiedzi Mr.S
thomasrutter

2

Dodatkowa odpowiedź

Jeśli nie musisz wiedzieć, które części plików się różnią, możesz użyć sumy kontrolnej pliku. Można to zrobić na wiele sposobów, używając md5sumlub sha256sum. Zasadniczo każdy z nich wyświetla ciąg znaków, do którego hash zawartości pliku. Jeśli oba pliki są takie same, ich skrót również będzie taki sam. Jest to często używane podczas pobierania oprogramowania, takiego jak obrazy ISO instalacji Ubuntu. Są często używane do sprawdzania integralności pobranych treści.

Rozważ poniższy skrypt, w którym możesz podać dwa pliki jako argumenty, a plik powie ci, czy są takie same, czy nie.

#!/bin/bash

# Check if both files exist  
if ! [ -e "$1"  ];
then
    printf "%s doesn't exist\n" "$1"
    exit 2
elif ! [ -e "$2" ]
then
    printf "%s doesn't exist\n" "$2"
    exit 2
fi

# Get checksums of eithe file
file1_sha=$( sha256sum "$1" | awk '{print $1}')
file2_sha=$( sha256sum "$2" | awk '{print $1}')

# Compare the checksums
if [ "x$file1_sha" = "x$file2_sha" ]
then
    printf "Files %s and %s are the same\n" "$1" "$2"
    exit 0
else
    printf "Files %s and %s are different\n" "$1" "$2"
    exit 1
fi

Przykładowy przebieg:

$ ./compare_files.sh /etc/passwd ./passwd_copy.txt                                                                
Files /etc/passwd and ./passwd_copy.txt are the same
$ echo $?
0
$ ./compare_files.sh /etc/passwd /etc/default/grub                                                                
Files /etc/passwd and /etc/default/grub are different
$ echo $?
1

Starsza odpowiedź

Ponadto istnieje commpolecenie, które porównuje dwa posortowane pliki i daje wynik w 3 kolumnach: kolumna 1 dla elementów unikalnych dla pliku nr 1, kolumna 2 dla elementów unikalnych dla pliku nr 2 i kolumna 3 dla elementów obecnych w obu plikach.

Aby pominąć dowolną kolumnę, możesz użyć przełączników -1, -2 i -3. Użycie -3 pokaże linie, które się różnią.

Poniżej możesz zobaczyć zrzut ekranu polecenia w akcji.

wprowadź opis zdjęcia tutaj

Jest tylko jeden wymóg - pliki muszą być posortowane, aby można je było poprawnie porównać. sortw tym celu można użyć polecenia. Poniżej znajduje się inny zrzut ekranu, w którym pliki są sortowane, a następnie porównywane. Linie zaczynające się od lewej bellong tylko do pliku_1, linie zaczynające się w kolumnie 2 należą tylko do pliku_2

wprowadź opis zdjęcia tutaj


@DavidFoerster ciężko jest edytować na telefonie komórkowym :)
Skończone

2

Zainstaluj git i użyj

$ git diff filename1 filename2

Otrzymasz wyjście w ładnym, kolorowym formacie

Instalacja Git

$ apt-get update
$ apt-get install git-core

2

colcmp.sh

Porównuje pary nazwa / wartość w 2 plikach w formacie name value\n. Zapisuje namedo, Output_filejeśli zmieniono. Wymaga bash v4 + dla tablic asocjacyjnych .

Stosowanie

$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2

Plik wyjściowy

$ cat Output_File
User3 has changed

Źródło (colcmp.sh)

cmp -s "$1" "$2"
case "$?" in
    0)
        echo "" > Output_File
        echo "files are identical"
        ;;
    1)
        echo "" > Output_File
        cp "$1" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.array1.tmp.sh
        chmod 755 ~/.colcmp.array1.tmp.sh
        declare -A A1
        source ~/.colcmp.array1.tmp.sh

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

        USERSWHODIDNOTCHANGE=
        for i in "${!A1[@]}"; do
            if [ "${A2[$i]+x}" = "" ]; then
                echo "$i was removed"
                echo "$i has changed" > Output_File
            fi
        done
        for i in "${!A2[@]}"; do
            if [ "${A1[$i]+x}" = "" ]; then
                echo "$i was added as '${A2[$i]}'"
                echo "$i has changed" > Output_File
            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
                echo "$i changed from '${A1[$i]}' to '${A2[$i]}'"
                echo "$i has changed" > Output_File
            else
                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
            fi
        done
        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
        ;;
    *)
        echo "error: file not found, access denied, etc..."
        echo "usage: ./colcmp.sh File_1.txt File_2.txt"
        ;;
esac

Wyjaśnienie

Podział kodu i jego znaczenie, o ile mi wiadomo. Z zadowoleniem przyjmuję zmiany i sugestie.

Podstawowe porównanie plików

cmp -s "$1" "$2"
case "$?" in
    0)
        # match
        ;;
    1)
        # compare
        ;;
    *)
        # error
        ;;
esac

cmp ustawi wartość $? w następujący sposób :

  • 0 = pliki pasują
  • 1 = pliki się różnią
  • 2 = błąd

Zdecydowałem się użyć instrukcji case .. esac do oceny $? ponieważ wartość $? zmienia się po każdym poleceniu, w tym test ([).

Alternatywnie mogłem użyć zmiennej do przechowywania wartości $? :

cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
    # match
elif [ $CMPRESULT -eq 1 ]; then
    # compare
else
    # error
fi

Powyżej robi to samo co instrukcja case. IDK, który lubię bardziej.

Wyczyść dane wyjściowe

        echo "" > Output_File

Powyżej usuwa plik wyjściowy, więc jeśli żaden użytkownik się nie zmieni, plik wyjściowy będzie pusty.

Robię to wewnątrz instrukcji case , aby plik wyjściowy pozostał niezmieniony po błędzie.

Skopiuj plik użytkownika do skryptu powłoki

        cp "$1" ~/.colcmp.arrays.tmp.sh

Powyżej kopiuje plik_1.txt do katalogu domowego bieżącego użytkownika.

Na przykład, jeśli bieżącym użytkownikiem jest John, powyższe byłoby takie samo jak cp „File_1.txt” /home/john/.colcmp.arrays.tmp.sh

Ucieczka ze znaków specjalnych

Zasadniczo jestem paranoikiem. Wiem, że te znaki mogą mieć specjalne znaczenie lub wykonywać zewnętrzny program, gdy są uruchamiane w skrypcie w ramach przypisywania zmiennych:

  • `- back-tick - wykonuje program i dane wyjściowe tak, jakby dane wyjściowe były częścią skryptu
  • $ - znak dolara - zwykle poprzedza zmienną
  • $ {} - pozwala na bardziej złożone podstawienie zmiennych
  • $ () - idk co robi, ale myślę, że może wykonać kod

Co ja nie wiem , jak bardzo nie wiem o bash. Nie wiem, jakie inne postacie mogą mieć specjalne znaczenie, ale chcę uciec przed nimi wszystkimi odwrotnym ukośnikiem:

        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh

sed potrafi znacznie więcej niż dopasowywanie wzorców wyrażeń regularnych . Wzorzec skryptu „s / (find) / (replace) /” konkretnie wykonuje dopasowanie wzorca.

„s / (znajdź) / (zamień) / (modyfikatory)”

w języku angielskim: przechwytuj dowolne znaki interpunkcyjne lub specjalne jako grupę kaputur 1 (\\ 1)

w języku angielskim: poprzedź wszystkie znaki specjalne odwrotnym ukośnikiem

  • (modyfikatory) = g
    • g = globalnie zamień

w języku angielskim: jeśli w tym samym wierszu znaleziono więcej niż jedno dopasowanie, zamień je wszystkie

Skomentuj cały skrypt

        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh

Powyżej używa wyrażenia regularnego, aby poprzedzić każdą linię ~ / .colcmp.arrays.tmp.sh znakiem komentarza bash ( # ). Robię to, ponieważ później zamierzam wykonać ~ / .colcmp.arrays.tmp.sh za pomocą polecenia source i ponieważ nie wiem na pewno cały format pliku_1.txt .

Nie chcę przypadkowo wykonać dowolnego kodu. Nie sądzę, żeby ktokolwiek to zrobił.

„s / (znajdź) / (zamień) /”

w języku angielskim: przechwytuj każdą linię jako grupę caputure 1 (\\ 1)

w języku angielskim: zamień każdą linię na symbol funta, a następnie linię, która została zastąpiona

Konwertuj wartość użytkownika na A1 [użytkownik] = „wartość”

        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh

Powyżej znajduje się rdzeń tego skryptu.

  • przekonwertuj to: #User1 US
    • do tego: A1[User1]="US"
    • lub to: A2[User1]="US"(dla drugiego pliku)

„s / (znajdź) / (zamień) /”

po angielsku:

  • wymagają, ale ignoruj ​​wiodące znaki komentarza (#)
  • zignoruj ​​wiodące białe znaki
  • przechwyć pierwsze słowo jako grupę 1 wielkich liter (\\ 1)
  • wymagają spacji (lub tabulacji lub białych znaków)
    • który zostanie zastąpiony znakiem równości, ponieważ
    • nie jest częścią żadnej grupy przechwytywania i dlatego
    • wzorzec (zamień) umieszcza znak równości między grupą przechwytywania 1 a grupą przechwytywania 2
  • przechwyć resztę linii jako grupę przechwytywania 2

  • (zamień) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"

    • A1 \\ [- dosłowne znaki, A1[aby rozpocząć przypisanie tablicy w tablicy o nazwieA1
    • \\ 1 = grupa przechwytywania 1 - która nie zawiera wiodącego skrótu (#) i nie obejmuje wiodących białych znaków - w tym przypadku grupa przechwytywania 1 jest używana do ustawienia nazwy pary nazwa / wartość w tablicy asocjacyjnej bash.
    • \\] = \ "= dosłowne znaki ]="
      • ]= bliskie przypisanie tablicy, np. A1[Użytkownik1 ]="USA"
      • = = operator przypisania, np. zmienna = wartość
      • " = podaj wartość, aby uchwycić spacje ... chociaż teraz, gdy o tym myślę, łatwiej byłoby pozwolić kodowi powyżej, który odwraca wszystko, także do znaków odwrotnego ukośnika.
    • \\ 1 = grupa przechwytywania 2 - w tym przypadku wartość pary nazwa / wartość
    • "= zamykając wartość cytatu, aby uchwycić spacje

w języku angielskim: zastąp każdą linię w formacie #name valueoperatorem przypisania tablicy w formacieA1[name]="value"

Uczyń plik wykonywalny

        chmod 755 ~/.colcmp.arrays.tmp.sh

Powyżej używa chmod, aby plik skryptu tablicowego był wykonywalny.

Nie jestem pewien, czy jest to konieczne.

Zadeklaruj tablicę asocjacyjną (bash v4 +)

        declare -A A1

Wielka-A oznacza, że ​​zadeklarowane zmienne będą tablicami asocjacyjnymi .

Dlatego skrypt wymaga bash v4 lub nowszego.

Wykonaj nasz skrypt Array Variable Assignment Script

        source ~/.colcmp.arrays.tmp.sh

Mamy już:

  • przekonwertowaliśmy nasz plik z linii User valuena linie A1[User]="value",
  • uczyniło go wykonywalnym (być może), i
  • zadeklarował A1 jako tablicę asocjacyjną ...

Powyżej my źródła skryptu, aby uruchomić go w bieżącej powłoki. Robimy to, abyśmy mogli zachować wartości zmiennych ustawiane przez skrypt. Jeśli skrypt zostanie wykonany bezpośrednio, odradza się nowa powłoka, a wartości zmiennych są tracone po wyjściu z nowej powłoki, a przynajmniej tak rozumiem.

To powinna być funkcja

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

Robimy to samo za 1 USD i A1 , co za 2 USD i A2 . To naprawdę powinna być funkcja. Myślę, że w tym momencie ten skrypt jest dość mylący i działa, więc go nie naprawię.

Wykryj usuniętych użytkowników

        for i in "${!A1[@]}"; do
            # check for users removed
        done

Powyższe pętle za pomocą kluczy tablicy asocjacyjnej

            if [ "${A2[$i]+x}" = "" ]; then

Powyżej używa podstawienia zmiennej w celu wykrycia różnicy między wartością, która nie jest ustawiona, a zmienną, która została jawnie ustawiona na ciąg o zerowej długości.

Najwyraźniej istnieje wiele sposobów sprawdzenia, czy zmienna została ustawiona . Wybrałem ten, który ma najwięcej głosów.

                echo "$i has changed" > Output_File

Powyżej dodaje użytkownika $ i do pliku_wyjściowego

Wykryj dodanych lub zmienionych użytkowników

        USERSWHODIDNOTCHANGE=

Powyżej usuwa zmienną, dzięki czemu możemy śledzić użytkowników, którzy się nie zmienili.

        for i in "${!A2[@]}"; do
            # detect users added, changed and not changed
        done

Powyższe pętle za pomocą kluczy tablicy asocjacyjnej

            if ! [ "${A1[$i]+x}" != "" ]; then

Powyżej używa podstawienia zmiennej, aby sprawdzić, czy zmienna została ustawiona .

                echo "$i was added as '${A2[$i]}'"

Ponieważ $ i jest kluczem tablicy (nazwa użytkownika) $ A2 [$ i] powinien zwrócić wartość powiązaną z bieżącym użytkownikiem z pliku_2.txt .

Na przykład jeśli $ i to Użytkownik1 , powyższy tekst brzmi jako $ {A2 [Użytkownik1]}

                echo "$i has changed" > Output_File

Powyżej dodaje użytkownika $ i do pliku_wyjściowego

            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then

Ponieważ $ i jest kluczem tablicy (nazwa użytkownika) $ A1 [$ i] powinien zwrócić wartość powiązaną z bieżącym użytkownikiem z pliku_1.txt , a $ A2 [$ i] powinien zwrócić wartość z pliku_2.txt .

Powyżej porównuje powiązane wartości dla użytkownika $ i z obu plików.

                echo "$i has changed" > Output_File

Powyżej dodaje użytkownika $ i do pliku_wyjściowego

                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"

Powyżej tworzy oddzieloną przecinkami listę użytkowników, którzy się nie zmienili. Uwaga: na liście nie ma spacji, w przeciwnym razie należałoby zacytować następny czek.

        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi

Powyżej zgłasza wartość $ USERSWHODIDNOTCHANGE, ale tylko jeśli wartość jest w US $ USERSWHODIDNOTCHANGE . Sposób zapisu: $ USERSWHODIDNOTCHANGE nie może zawierać spacji. Jeśli potrzebuje spacji, powyższe można przepisać w następujący sposób:

        if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.