Bagaimana membandingkan dua file

83

Jadi pada dasarnya yang ingin saya lakukan adalah membandingkan dua file dengan baris demi kolom 2. Bagaimana saya bisa mencapai ini?

File_1.txt:

User1 US
User2 US
User3 US

File_2.txt:

User1 US
User2 US
User3 NG

Berkas keluaran:

User3 has changed
Roboman1723
sumber
11
Gunakandiff "File_1.txt" "File_2.txt"
Pandya
Kunjungi juga: askubuntu.com/q/12473
Pandya

Jawaban:

92

Lihatlah diffperintahnya. Ini alat yang bagus, dan Anda bisa membacanya dengan mengetik di man diffterminal Anda.

Perintah yang ingin Anda lakukan adalah diff File_1.txt File_2.txtyang akan menampilkan perbedaan antara keduanya dan akan terlihat seperti ini:

masukkan deskripsi gambar di sini

Catatan singkat tentang membaca output dari perintah ketiga: 'Panah' ( <dan >) merujuk pada nilai baris di file kiri ( <) vs file kanan ( >), dengan file kiri menjadi yang Anda masukkan pertama di baris perintah, dalam hal iniFile_1.txt

Selain itu, Anda mungkin melihat perintah ke-4 adalah diff ... | tee Output_Fileini mem-pipe hasil dari diffmenjadi tee, yang kemudian menempatkan output itu ke dalam file, sehingga Anda dapat menyimpannya untuk nanti jika Anda tidak ingin melihat semuanya di konsol saat itu juga.

Mitch
sumber
Bisakah ini melakukan file lain (seperti gambar)? Atau hanya sebatas dokumen?
Gregory Opera
2
Sejauh yang saya tahu, ini terbatas pada file teks. Kode akan berfungsi, karena pada dasarnya berupa teks, tetapi semua file biner (gambar mana) akan dibuang begitu saja. Anda BISA membandingkan untuk melihat apakah mereka identik dengan melakukan: diff file1 file2 -s. Berikut ini contohnya: imgur.com/ShrQx9x
Mitch
Apakah ada cara untuk mewarnai output? Saya ingin menyimpannya CLI-only, tetapi dengan beberapa lagi ... sentuhan manusia.
Lazar Ljubenović
36

Atau Anda dapat menggunakan Meld Diff

Meld membantu Anda membandingkan file, direktori, dan proyek yang dikontrol versi. Ini menyediakan perbandingan dua dan tiga arah baik file dan direktori, dan memiliki dukungan untuk banyak sistem kontrol versi populer.

Instal dengan menjalankan:

sudo apt-get install meld

Contoh Anda:

masukkan deskripsi gambar di sini

Bandingkan direktori:

masukkan deskripsi gambar di sini

Contoh dengan teks lengkap:

masukkan deskripsi gambar di sini

Achu
sumber
18

Anda dapat menggunakan vimdiff .

Contoh:

vimdiff  file1  file2
Nyonya
sumber
1
yang ini memiliki warna
Jake Toronto
Ini membantu saya karena menunjukkan file baris pertama saya berakhir dalam dosdan yang kedua di unix.
LoMaPh
13

FWIW, saya lebih suka apa yang saya dapatkan dengan output berdampingan dari diff

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

akan memberikan sesuatu seperti:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
Mike Reardon
sumber
10

Anda dapat menggunakan perintah cmp:

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

output akan menjadi

a b differ: byte 25, line 3 is 125 U 116 N
Maythux
sumber
cmpjauh lebih cepat daripada diffjika semua yang Anda inginkan adalah kode pengembalian.
stevesliva
8

Meldadalah alat yang sangat hebat. Tetapi Anda juga dapat menggunakan diffuseuntuk membandingkan dua file secara visual:

diffuse file1.txt file2.txt

masukkan deskripsi gambar di sini

Meysam
sumber
7

Litteraly menempel pada pertanyaan (file1, file2, outputfile dengan pesan "telah berubah") skrip di bawah ini berfungsi.

Salin skrip ke file kosong, simpan sebagai compare.py, buat dapat dieksekusi, jalankan dengan perintah:

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

Naskah:

#!/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")

Dengan beberapa baris tambahan, Anda dapat membuatnya mencetak ke file output, atau ke terminal, tergantung pada apakah file output didefinisikan:

Untuk mencetak ke file:

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

Untuk mencetak ke jendela terminal:

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

Naskah:

#!/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"
Yakub Vlijm
sumber
4

Cara mudah adalah menggunakan colordiff, yang berperilaku seperti difftetapi mewarnai hasilnya. Ini sangat membantu untuk membaca perbedaan. Menggunakan contoh Anda,

$ 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

di mana uopsi memberikan diff terpadu. Ini adalah bagaimana diff berwarna ini terlihat seperti:

masukkan deskripsi gambar di sini

Instal colordiffdengan menjalankan sudo apt-get install colordiff.

edwinksl
sumber
1
Jika Anda ingin warna, saya menemukan diff dibangun ke vim untuk benar-benar mudah digunakan, seperti dalam jawaban oleh Mr.S
thomasrutter
2

Jawaban tambahan

Jika tidak perlu tahu bagian file yang berbeda, Anda dapat menggunakan checksum file. Ada banyak cara untuk melakukannya, menggunakan md5sumatau sha256sum. Pada dasarnya, masing-masing dari mereka menampilkan string yang hash isi file. Jika kedua file itu sama, hash mereka juga akan sama. Ini sering digunakan ketika Anda mengunduh perangkat lunak, seperti gambar instalasi iso Ubuntu. Mereka sering digunakan untuk memverifikasi integritas konten yang diunduh.

Pertimbangkan skrip di bawah ini, tempat Anda dapat memberikan dua file sebagai argumen, dan file tersebut akan memberi tahu Anda apakah keduanya sama atau tidak.

#!/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

Contoh dijalankan:

$ ./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

Jawaban yang lebih tua

Selain itu ada commperintah, yang membandingkan dua file yang diurutkan, dan memberikan output dalam 3 kolom: kolom 1 untuk item unik ke file # 1, kolom 2 untuk item unik untuk file # 2, dan kolom 3 untuk item yang ada di kedua file.

Untuk menekan salah satu kolom, Anda dapat menggunakan sakelar -1, -2, dan -3. Menggunakan -3 akan menunjukkan garis yang berbeda.

Di bawah ini Anda dapat melihat tangkapan layar dari perintah yang sedang beraksi.

masukkan deskripsi gambar di sini

Hanya ada satu persyaratan - file harus diurutkan agar dapat dibandingkan dengan benar. sortperintah dapat digunakan untuk tujuan itu. Di bawah adalah screenshot lain, di mana file diurutkan dan kemudian dibandingkan. Baris yang dimulai pada bellong kiri hanya untuk File_1, baris yang dimulai pada kolom 2 hanya milik File_2

masukkan deskripsi gambar di sini

Sergiy Kolodyazhnyy
sumber
@DavidFoerster agak sulit untuk mengedit di ponsel :) Selesai sekarang, meskipun
Sergiy Kolodyazhnyy
2

Instal git dan gunakan

$ git diff filename1 filename2

Dan Anda akan mendapatkan output dalam format berwarna yang bagus

Instalasi git

$ apt-get update
$ apt-get install git-core
Eric Korolev
sumber
2

colcmp.sh

Membandingkan pasangan nama / nilai dalam 2 file dalam format name value\n. Menulis nameuntuk Output_filejika diubah. Membutuhkan bash v4 + untuk array asosiatif .

Pemakaian

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

Berkas keluaran

$ cat Output_File
User3 has changed

Sumber (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

Penjelasan

Rincian kode dan apa artinya, sejauh yang saya mengerti. Saya menerima suntingan dan saran.

Bandingkan File Dasar

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

cmp akan menetapkan nilai $? sebagai berikut :

  • 0 = file cocok
  • 1 = file berbeda
  • 2 = kesalahan

Saya memilih untuk menggunakan case .. pernyataan esac untuk evalute $? karena nilai $? berubah setelah setiap perintah, termasuk tes ([).

Atau saya bisa menggunakan variabel untuk menyimpan nilai $? :

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

Di atas melakukan hal yang sama dengan pernyataan kasus. IDK yang saya sukai lebih baik.

Bersihkan Output

        echo "" > Output_File

Di atas menghapus file output jadi jika tidak ada pengguna yang berubah, file output akan kosong.

Saya melakukan ini di dalam pernyataan kasus sehingga Output_file tetap tidak berubah pada kesalahan.

Salin File Pengguna ke Shell Script

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

Di atas menyalin File_1.txt ke direktori home pengguna saat ini.

Misalnya, jika pengguna saat ini adalah john, di atas akan sama dengan cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Escape Karakter Khusus

Pada dasarnya, saya paranoid. Saya tahu bahwa karakter ini dapat memiliki arti khusus atau menjalankan program eksternal ketika dijalankan dalam skrip sebagai bagian dari penugasan variabel:

  • `- centang-ulang - menjalankan program dan output seolah-olah output adalah bagian dari skrip Anda
  • Tanda $ - dolar - biasanya awalan variabel
  • $ {} - memungkinkan untuk substitusi variabel yang lebih kompleks
  • $ () - idk apa ini tetapi saya pikir itu dapat mengeksekusi kode

Yang saya tidak tahu adalah seberapa banyak saya tidak tahu tentang bash. Saya tidak tahu apa karakter lain yang mungkin memiliki makna khusus, tetapi saya ingin menghindarinya dengan backslash:

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

sed dapat melakukan lebih dari sekadar pencocokan pola ekspresi reguler . Pola skrip "s / (find) / (ganti) /" secara khusus melakukan kecocokan pola.

"s / (temukan) / (ganti) / (pengubah)"

dalam bahasa Inggris: tangkap tanda baca atau karakter khusus apa pun sebagai grup caputure 1 (\\ 1)

  • (ganti) = \\ 1
    • \\ = karakter literal (\\) yaitu garis miring terbalik
    • \\ 1 = kelompok tangkap 1

dalam bahasa Inggris: awali semua karakter khusus dengan garis miring terbalik

  • (pengubah) = g
    • g = ganti secara global

dalam bahasa Inggris: jika lebih dari satu kecocokan ditemukan pada baris yang sama, gantilah semuanya

Komentari Seluruh Script

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

Di atas menggunakan ekspresi reguler untuk mengawali setiap baris ~ / .colcmp.arrays.tmp.sh dengan karakter komentar bash ( # ). Saya melakukan ini karena nanti saya bermaksud untuk mengeksekusi ~ / .colcmp.arrays.tmp.sh menggunakan perintah sumber dan karena saya tidak tahu pasti seluruh format File_1.txt .

Saya tidak ingin secara tidak sengaja mengeksekusi kode arbitrer. Saya tidak berpikir ada yang melakukannya.

"s / (temukan) / (ganti) /"

dalam bahasa Inggris: tangkap setiap baris sebagai grup caputure 1 (\\ 1)

  • (ganti) = # \\ 1
    • # = Karakter literal (#) yaitu simbol pound atau hash
    • \\ 1 = kelompok tangkap 1

dalam bahasa Inggris: ganti setiap baris dengan simbol pound diikuti dengan garis yang diganti

Konversi Nilai Pengguna ke A1 [Pengguna] = "nilai"

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

Di atas adalah inti dari skrip ini.

  • ubah ini: #User1 US
    • untuk ini: A1[User1]="US"
    • atau ini: A2[User1]="US"(untuk file ke-2)

"s / (temukan) / (ganti) /"

dalam Bahasa Inggris:

  • membutuhkan tetapi mengabaikan karakter komentar utama (#)
  • abaikan spasi putih terkemuka
  • tangkap kata pertama sebagai grup caputure 1 (\\ 1)
  • memerlukan spasi (atau tab, atau spasi putih)
    • yang akan diganti dengan tanda sama dengan karena
    • itu bukan bagian dari grup penangkap, dan karena
    • pola (ganti) menempatkan tanda sama dengan antara kelompok tangkap 1 dan kelompok tangkap 2
  • tangkap sisa baris sebagai grup tangkap 2

  • (ganti) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"

    • A1 \\ [- karakter literal A1[untuk memulai penugasan array dalam sebuah array bernamaA1
    • \\ 1 = grup tangkap 1 - yang tidak termasuk hash utama (#) dan tidak termasuk spasi putih - dalam hal ini grup tangkap 1 sedang digunakan untuk mengatur nama pasangan nama / nilai dalam array asosiatif bash.
    • \\] = \ "= karakter literal ]="
      • ]= tugas array dekat misalnya A1[User1 ]="US"
      • = = operator penugasan misalnya variabel = nilai
      • " = nilai kutipan untuk menangkap spasi ... walaupun sekarang saya berpikir tentang hal itu, akan lebih mudah untuk membiarkan kode di atas yang backslash semuanya untuk juga karakter backslash spasi.
    • \\ 1 = capture group 2 - dalam hal ini, nilai pasangan nama / nilai
    • "= menutup nilai penawaran untuk mengambil spasi

dalam bahasa Inggris: ganti setiap baris dalam format #name valuedengan operator penugasan array dalam formatA1[name]="value"

Jadikan Dapat Dieksekusi

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

Di atas menggunakan chmod untuk membuat file skrip array dapat dieksekusi.

Saya tidak yakin apakah ini perlu.

Deklarasikan Array Asosiatif (bash v4 +)

        declare -A A1

Capital -A menunjukkan bahwa variabel yang dideklarasikan akan menjadi array asosiatif .

Inilah sebabnya mengapa skrip membutuhkan bash v4 atau lebih tinggi.

Jalankan Script Assignment Variable Assignment kami

        source ~/.colcmp.arrays.tmp.sh

Kita sudah:

  • mengkonversi file kami dari baris User valueke baris A1[User]="value",
  • menjadikannya executable (mungkin), dan
  • menyatakan A1 sebagai array asosiatif ...

Di atas kami sumber script untuk menjalankannya di shell saat ini Kami melakukan ini agar kami dapat menyimpan nilai variabel yang ditetapkan oleh skrip. Jika Anda menjalankan skrip secara langsung, itu memunculkan shell baru, dan nilai-nilai variabel hilang ketika shell baru keluar, atau setidaknya itu adalah pemahaman saya.

Ini Seharusnya Berfungsi

        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

Kami melakukan hal yang sama untuk $ 1 dan A1 yang kami lakukan untuk $ 2 dan A2 . Itu benar-benar harus menjadi fungsi. Saya pikir pada titik ini skrip ini cukup membingungkan dan berfungsi, jadi saya tidak akan memperbaikinya.

Deteksi Pengguna Dihapus

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

Di atas loop melalui kunci array asosiatif

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

Di atas menggunakan substitusi variabel untuk mendeteksi perbedaan antara nilai yang tidak disetel vs variabel yang telah secara eksplisit diatur ke string panjang nol.

Rupanya, ada banyak cara untuk melihat apakah suatu variabel telah ditetapkan . Saya memilih satu dengan suara terbanyak.

                echo "$i has changed" > Output_File

Di atas menambahkan pengguna $ i ke Output_File

Deteksi Pengguna Ditambahkan atau Diubah

        USERSWHODIDNOTCHANGE=

Di atas menghapus variabel sehingga kami dapat melacak pengguna yang tidak berubah.

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

Di atas loop melalui kunci array asosiatif

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

Di atas menggunakan substitusi variabel untuk melihat apakah suatu variabel telah ditetapkan .

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

Karena $ i adalah kunci array (nama pengguna) $ A2 [$ i] harus mengembalikan nilai yang terkait dengan pengguna saat ini dari File_2.txt .

Misalnya, jika $ i adalah User1 , yang di atas berbunyi $ {A2 [User1]}

                echo "$i has changed" > Output_File

Di atas menambahkan pengguna $ i ke Output_File

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

Karena $ i adalah kunci array (nama pengguna) $ A1 [$ i] harus mengembalikan nilai yang terkait dengan pengguna saat ini dari File_1.txt , dan $ A2 [$ i] harus mengembalikan nilai dari File_2.txt .

Di atas membandingkan nilai yang terkait untuk pengguna $ i dari kedua file ..

                echo "$i has changed" > Output_File

Di atas menambahkan pengguna $ i ke Output_File

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

Di atas membuat daftar pengguna yang dipisahkan koma dan tidak berubah. Perhatikan tidak ada spasi dalam daftar, atau cek berikutnya perlu dikutip.

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

Di atas melaporkan nilai $ USERSWHODIDNOTCHANGE tetapi hanya jika ada nilai dalam $ USERSWHODIDNOTCHANGE . Cara ini ditulis, $ USERSWHODIDNOTCHANGE tidak boleh berisi spasi. Jika memang membutuhkan spasi, di atas dapat ditulis ulang sebagai berikut:

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