Come confrontare due file


83

Quindi, fondamentalmente, ciò che voglio fare è confrontare due file riga per colonna 2. Come posso ottenere questo risultato?

File_1.txt:

User1 US
User2 US
User3 US

File_2.txt:

User1 US
User2 US
User3 NG

File di uscita:

User3 has changed

11
Usadiff "File_1.txt" "File_2.txt"
Pandya il

Risposte:


92

Guarda il diffcomando. È un buon strumento e puoi leggere tutto scrivendo man diffnel tuo terminale.

Il comando che vorrai fare è diff File_1.txt File_2.txtche produrrà la differenza tra i due e dovrebbe assomigliare a questo:

inserisci qui la descrizione dell'immagine

Una breve nota sulla lettura dell'output dal terzo comando: Le 'frecce' ( <e >) si riferiscono a quale sia il valore della riga nel file di sinistra ( <) rispetto al file di destra ( >), con il file di sinistra che è quello che hai inserito prima nella riga di comando, in questo casoFile_1.txt

Inoltre, potresti notare che il quarto comando è che diff ... | tee Output_Fileinoltra i risultati diffin a tee, che quindi inserisce l'output in un file, in modo da poterlo salvare in un secondo momento se non vuoi vederlo tutto sulla console proprio in quel secondo.


Questo può fare altri file (come le immagini)? O è limitato ai soli documenti?
Gregory Opera,

2
Per quanto ne so, è limitato ai file di testo. Il codice funzionerà, dato che è essenzialmente testo, ma qualsiasi file binario (quali immagini sono) verrà semplicemente spazzato via. È possibile confrontare per vedere se sono identiche facendo: diff file1 file2 -s. Ecco un esempio: imgur.com/ShrQx9x
Mitch

C'è un modo per colorare l'output? Vorrei mantenerlo solo CLI, ma con un po 'più di ... tocco umano.
Lazar Ljubenović,

36

Oppure puoi usare Meld Diff

Combinazione ti aiuta a confrontare file, directory e progetti controllati dalla versione. Fornisce un confronto a due e tre vie di file e directory e ha il supporto per molti sistemi di controllo di versione popolari.

Installa eseguendo:

sudo apt-get install meld

Il tuo esempio:

inserisci qui la descrizione dell'immagine

Confronta directory:

inserisci qui la descrizione dell'immagine

Esempio con pieno di testo:

inserisci qui la descrizione dell'immagine


18

Puoi usare vimdiff .

Esempio:

vimdiff  file1  file2

1
questo ha i colori
Jake Toronto,

Questo mi ha aiutato in quanto ha mostrato che la fine del mio primo file era in dose il secondo in unix.
LoMaPh

13

FWIW, preferisco quello che ottengo con l'output side-by-side di diff

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

darebbe qualcosa del tipo:

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

10

Puoi usare il comando cmp:

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

l'uscita sarebbe

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

cmpè molto più veloce che diffse tutto ciò che desideri è il codice di ritorno.
stevesliva,

8

Meldè uno strumento davvero eccezionale. Ma puoi anche usare diffuseper confrontare visivamente due file:

diffuse file1.txt file2.txt

inserisci qui la descrizione dell'immagine


7

Litteraly aderisce alla domanda (file1, file2, outputfile con il messaggio "è cambiato") lo script seguente funziona.

Copia lo script in un file vuoto, salvalo come compare.py, rendilo eseguibile, eseguilo con il comando:

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

Il copione:

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

Con alcune righe extra, puoi farlo stampare su un file di output o sul terminale, a seconda che sia definito il file di output:

Per stampare su un file:

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

Per stampare sulla finestra del terminale:

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

Il copione:

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

Un modo semplice è usare colordiff, che si comporta come diffma colora il suo output. Questo è molto utile per leggere le differenze. Usando il tuo esempio,

$ 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

dove l' uopzione fornisce un diff unificato. Ecco come appare il diff colorato:

inserisci qui la descrizione dell'immagine

Installa colordiffeseguendo sudo apt-get install colordiff.


1
Se vuoi colori, trovo che il diff incorporato in VIM sia davvero facile da usare, come nella risposta di Mr.S
thomasrutter,

2

Risposta aggiuntiva

Se non è necessario sapere quali parti dei file differiscono, è possibile utilizzare il checksum del file. Ci sono molti modi per farlo, usando md5sumo sha256sum. Fondamentalmente, ognuno di essi genera una stringa in cui l'hash del contenuto di un file. Se i due file sono uguali, anche il loro hash sarà lo stesso. Questo è spesso usato quando si scaricano software, come immagini iso di installazione di Ubuntu. Vengono spesso utilizzati per verificare l'integrità di un contenuto scaricato.

Considera lo script di seguito, in cui puoi fornire due file come argomenti e il file ti dirà se sono uguali o meno.

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

Esecuzione di esempio:

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

Risposta precedente

Inoltre c'è il commcomando, che confronta due file ordinati, e fornisce l'output in 3 colonne: colonna 1 per elementi univoci al file n. 1, colonna 2 per elementi univoci al file n. 2 e colonna 3 per gli elementi presenti in entrambi i file.

Per sopprimere una delle colonne è possibile utilizzare gli switch -1, -2 e -3. L'uso di -3 mostrerà le linee che differiscono.

Di seguito puoi vedere lo screenshot del comando in azione.

inserisci qui la descrizione dell'immagine

C'è solo un requisito: i file devono essere ordinati per poter essere confrontati correttamente. sortcomando può essere utilizzato a tale scopo. Bellow è un altro screenshot, in cui i file vengono ordinati e confrontati. Le righe che iniziano sulla sinistra a solo File_1, le righe che iniziano sulla colonna 2 appartengono solo a File_2

inserisci qui la descrizione dell'immagine


@DavidFoerster è un po 'difficile eseguire le modifiche sul cellulare :) Fatto adesso, però
Sergiy Kolodyazhnyy,

2

Installa git e usa

$ git diff filename1 filename2

E otterrai un output in un bel formato colorato

Installazione Git

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

2

colcmp.sh

Confronta le coppie nome / valore in 2 file nel formato name value\n. Scrive namea Output_filese modificato. Richiede bash v4 + per array associativi .

uso

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

File di uscita

$ cat Output_File
User3 has changed

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

Spiegazione

Analisi del codice e del suo significato, per quanto ne so. Accolgo con favore modifiche e suggerimenti.

Confronto file di base

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

cmp imposterà il valore di $? come segue :

  • 0 = i file corrispondono
  • 1 = i file differiscono
  • 2 = errore

Ho scelto di usare un'istruzione case .. esac per valutare $? perché il valore di $? cambia dopo ogni comando, incluso test ([).

In alternativa avrei potuto usare una variabile per contenere il valore di $? :

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

Sopra fa la stessa cosa dell'affermazione case. IDK che mi piace di più.

Cancella l'output

        echo "" > Output_File

Sopra cancella il file di output, quindi se nessun utente è cambiato, il file di output sarà vuoto.

Faccio questo all'interno delle dichiarazioni del caso in modo che Output_file rimanga invariato in caso di errore.

Copia il file utente in Shell Script

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

Sopra copia File_1.txt nella home directory dell'utente corrente.

Ad esempio, se l'utente corrente è john, quanto sopra sarebbe lo stesso di cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Fuga dai personaggi speciali

Fondamentalmente, sono paranoico. So che questi personaggi potrebbero avere un significato speciale o eseguire un programma esterno quando eseguiti in uno script come parte dell'assegnazione delle variabili:

  • `- back-tick - esegue un programma e l'output come se l'output fosse parte del tuo script
  • $ - simbolo del dollaro - di solito prefigura una variabile
  • $ {} - consente una sostituzione variabile più complessa
  • $ () - idk cosa fa ma penso che possa eseguire il codice

Quello che non so è quanto non so di bash. Non so quali altri personaggi potrebbero avere un significato speciale, ma voglio sfuggirli tutti con una barra rovesciata:

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

sed può fare molto di più della corrispondenza del modello di espressione regolare . Il modello di script "s / (trova) / (sostituisci) /" esegue specificamente la corrispondenza del modello.

"S / (find) / (sostituire) / (modificatori)"

in inglese: cattura la punteggiatura o il carattere speciale come gruppo caputure 1 (\\ 1)

  • (sostituisci) = \\\\\\ 1

in inglese: prefisso tutti i caratteri speciali con una barra rovesciata

  • (modificatori) = g
    • g = sostituzione globale

in inglese: se si trova più di una corrispondenza sulla stessa riga, sostituirli tutti

Commenta l'intero script

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

Sopra usa un'espressione regolare per aggiungere il prefisso a ogni riga di ~ / .colcmp.arrays.tmp.sh con un carattere di commento bash ( # ). Lo faccio perché in seguito intendo eseguire ~ / .colcmp.arrays.tmp.sh usando il comando source e perché non conosco per certo l'intero formato di File_1.txt .

Non voglio eseguire accidentalmente codice arbitrario. Non penso che lo faccia nessuno.

"S / (find) / (sostituire) /"

in inglese: cattura ogni riga come gruppo di caputure 1 (\\ 1)

  • (sostituisci) = # \\ 1
    • # = carattere letterale (#) cioè un simbolo di cancelletto o un cancelletto
    • \\ 1 = gruppo di acquisizione 1

in inglese: sostituisci ogni riga con un simbolo di cancelletto seguito dalla riga che è stata sostituita

Converti valore utente in A1 [Utente] = "valore"

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

Sopra è il nucleo di questa sceneggiatura.

  • converti questo: #User1 US
    • a questo: A1[User1]="US"
    • o questo: A2[User1]="US"(per il 2o file)

"S / (find) / (sostituire) /"

in inglese:

  • richiede ma ignora i caratteri di commento iniziali (#)
  • ignora i primi spazi bianchi
  • cattura la prima parola come gruppo caputure 1 (\\ 1)
  • richiede uno spazio (o una scheda o uno spazio bianco)
    • che verrà sostituito con un segno di uguale perché
    • non fa parte di alcun gruppo di acquisizione e perché
    • il modello (sostituisci) mette un segno di uguale tra il gruppo di acquisizione 1 e il gruppo di acquisizione 2
  • acquisire il resto della linea come gruppo di acquisizione 2

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

    • A1 \\ [- caratteri letterali A1[per iniziare l'assegnazione di array in un array chiamatoA1
    • \\ 1 = gruppo di acquisizione 1 - che non include l'hash iniziale (#) e non include spazi bianchi iniziali - in questo caso il gruppo di acquisizione 1 viene utilizzato per impostare il nome della coppia nome / valore nella matrice associativa bash.
    • \\] = \ "= caratteri letterali ]="
      • ]= assegnazione di array vicini, ad es. A1[Utente1 ]="USA"
      • = = operatore di assegnazione, ad es. variabile = valore
      • " = valore di citazione per catturare spazi ... anche se ora che ci penso, sarebbe stato più facile lasciare che il codice sopra che rovesciasse tutto per rovesciare anche i caratteri dello spazio.
    • \\ 1 = gruppo di acquisizione 2: in questo caso, il valore della coppia nome / valore
    • "= valore di virgoletta di chiusura per acquisire spazi

in inglese: sostituisci ogni riga nel formato #name valuecon un operatore di assegnazione di array nel formatoA1[name]="value"

Rendi eseguibile

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

Sopra usa chmod per rendere eseguibile il file di script dell'array.

Non sono sicuro che ciò sia necessario.

Declare Associative Array (bash v4 +)

        declare -A A1

La maiuscola -A indica che le variabili dichiarate saranno array associativi .

Questo è il motivo per cui lo script richiede bash v4 o superiore.

Esegui il nostro script di assegnazione delle variabili di array

        source ~/.colcmp.arrays.tmp.sh

Abbiamo già:

  • convertito il nostro file da righe di User valuea righe di A1[User]="value",
  • reso eseguibile (forse), e
  • ha dichiarato A1 come un array associativo ...

Sopra abbiamo fonte lo script da eseguire nella shell corrente. Facciamo così in modo da poter mantenere i valori delle variabili che vengono impostati dallo script. Se esegui direttamente lo script, viene generata una nuova shell e i valori delle variabili vengono persi quando esce la nuova shell, o almeno questa è la mia comprensione.

Questa dovrebbe essere una funzione

        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

Facciamo la stessa cosa per $ 1 e A1 che facciamo per $ 2 e A2 . Dovrebbe davvero essere una funzione. Penso che a questo punto questo script sia abbastanza confuso e funzioni, quindi non lo aggiusterò.

Rileva utenti rimossi

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

Sopra i cicli tramite chiavi array associative

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

Sopra utilizza la sostituzione delle variabili per rilevare la differenza tra un valore non impostato rispetto a una variabile che è stata esplicitamente impostata su una stringa di lunghezza zero.

Apparentemente, ci sono molti modi per vedere se è stata impostata una variabile . Ho scelto quello con il maggior numero di voti.

                echo "$i has changed" > Output_File

Sopra aggiunge l'utente $ i a Output_File

Rileva utenti aggiunti o modificati

        USERSWHODIDNOTCHANGE=

Sopra cancella una variabile in modo da poter tenere traccia degli utenti che non sono cambiati.

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

Sopra i cicli tramite chiavi array associative

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

Sopra usa la sostituzione delle variabili per vedere se è stata impostata una variabile .

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

Poiché $ i è la chiave dell'array (nome utente) $ A2 [$ i] dovrebbe restituire il valore associato all'utente corrente da File_2.txt .

Ad esempio, se $ i è Utente1 , quanto sopra si legge come $ {A2 [Utente1]}

                echo "$i has changed" > Output_File

Sopra aggiunge l'utente $ i a Output_File

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

Poiché $ i è la chiave dell'array (nome utente) $ A1 [$ i] dovrebbe restituire il valore associato all'utente corrente da File_1.txt e $ A2 [$ i] dovrebbe restituire il valore da File_2.txt .

Sopra confronta i valori associati per l'utente $ i da entrambi i file.

                echo "$i has changed" > Output_File

Sopra aggiunge l'utente $ i a Output_File

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

Sopra crea un elenco separato da virgole di utenti che non sono cambiati. Nota che non ci sono spazi nell'elenco, altrimenti il ​​prossimo controllo dovrebbe essere citato.

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

Sopra riporta il valore di $ USERSWHODIDNOTCHANGE ma solo se esiste un valore in $ USERSWHODIDNOTCHANGE . In questo modo, $ USERSWHODIDNOTCHANGE non può contenere spazi. Se necessita di spazi, sopra potrebbe essere riscritto come segue:

        if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.