Confronta due file riga per riga e genera la differenza in un altro file


121

Voglio confrontare file1 con file2 e generare un file3 che contiene le righe in file1 che non sono presenti in file2.


Ho provato diff ma genera alcuni numeri e altri simboli davanti a linee diverse che mi rendono difficile confrontare i file.
domenica

Risposte:


216

diff (1) non è la risposta, ma comm (1) sì.

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

Così

comm -2 -3 file1 file2 > file3

I file di input devono essere ordinati. Se non lo sono, ordinali prima. Questo può essere fatto con un file temporaneo o ...

comm -2 -3 <(sort file1) <(sort file2) > file3

a condizione che la tua shell supporti la sostituzione del processo (bash lo fa).


1
Ricordate che due file devono essere ordinati ed è unico nel suo genere
andy

6
Puoi raggruppare le opzioni insieme:comm -23
Paolo M

Cosa significa "ordinato"? Che le linee abbiano lo stesso ordine? Quindi probabilmente va bene per la maggior parte dei casi d'uso, come in, verificare quali righe sono state aggiunte confrontandole con una versione precedente di backup. Se le linee appena aggiunte non possono essere tra le linee esistenti, questo è più un problema.
Egor Hans

@EgorHans: se il file ha, ad esempio, righe contenenti numeri interi come "3 \ n1 \ n3 \ n2 \ n", le righe devono essere prima riordinate in ordine crescente o decrescente, ad esempio "\ 1 \ n2 \ n3 \ n3 \ n" con duplicati adiacente. Questo è "ordinato" ed entrambi i file devono essere ordinati in modo simile. Quando il file più recente ha nuove righe, non importa se si trovano "tra righe esistenti" perché dopo l'ordinamento non lo sono, sono ordinate.
sorpigal

48

L'utilità Unix diffè pensata esattamente per questo scopo.

$ diff -u file1 file2 > file3

Vedere il manuale e Internet per opzioni, diversi formati di output, ecc.


8
Quello non fa il lavoro richiesto; inserisce un mucchio di caratteri extra, anche con l'uso di opzioni della riga di comando suggerite in altre risposte.
xenocyon

20

Considera questo:
file a.txt:

abcd
efgh

file b.txt:

abcd

Puoi trovare la differenza con:

diff -a --suppress-common-lines -y a.txt b.txt

L'output sarà:

efgh 

Puoi reindirizzare l'output in un file di output (c.txt) utilizzando:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

Questo risponderà alla tua domanda:

"... che contiene le righe in file1 che non sono presenti in file2."


2
Ci sono due limitazioni a questa risposta: (1) funziona solo per righe brevi (meno di 80 caratteri per impostazione predefinita, anche se può essere modificato) e, cosa più importante, (2) aggiunge un "<" alla fine di ciascuna riga che deve essere tolta con un altro programma (es. awk, sed).
sergut

In molti casi, ti consigliamo anche di utilizzare -d, che farà del diffsuo meglio per trovare il più piccolo diff. -i, -E, -w, -BE --suppress-blank-emptypuò anche essere utile a volte, anche se non sempre. Se non sai cosa si adatta al tuo caso d'uso, prova diff --helpprima (che è generalmente una buona idea quando non sai cosa può fare un comando).
Egor Hans

Inoltre, usando --line-format =% L, impedisci a diff di generare caratteri extra (almeno, l'aiuto dice che funziona in questo modo, ma sta per provarlo).
Egor Hans

Anche questo è più breve e sembra funziona allo stesso stackoverflow.com/a/27667185/1179925
mrgloom

8

A volte diffè l'utilità di cui hai bisogno, ma a volte joinè più appropriata. I file devono essere preordinati o, se si utilizza una shell che supporta la sostituzione del processo come bash, ksh o zsh, è possibile eseguire l'ordinamento al volo.

join -v 1 <(sort file1) <(sort file2)

Dovresti ottenere una medaglia per questo! Era esattamente quello che stavo cercando nelle ultime 2 ore
Zatarra

7

Provare

sdiff file1 file2

Di solito funziona molto meglio nella maggior parte dei casi per me. Potresti voler ordinare i file prima, se l'ordine delle righe non è importante (ad esempio alcuni file di configurazione del testo).

Per esempio,

sdiff -w 185 file1.cfg file2.cfg

1
Bella utilità! Amo come segna le linee di differenziazione. Rende molto più facile confrontare le configurazioni. Questo insieme a sort è una combinazione mortale (ad esempio sdiff <(sort file1) <(sort file2))
jmagnusson

3

Se devi risolvere questo problema con coreutils, la risposta accettata è buona:

comm -23 <(sort file1) <(sort file2) > file3

Puoi anche usare sd (stream diff), che non richiede l'ordinamento né la sostituzione del processo e supporta flussi infiniti, in questo modo:

cat file1 | sd 'cat file2' > file3

Probabilmente non è un gran vantaggio in questo esempio, ma consideralo comunque; in alcuni casi non potrai usare commgrep -Fdiff .

Ecco un post sul blog che ho scritto sui diversi flussi sul terminale, che introduce sd.


3

Eppure, nessuna grepsoluzione?

  • righe che esistono solo in file2:

    grep -Fxvf file1 file2 > file3
  • righe che esistono solo in file1:

    grep -Fxvf file2 file1 > file3
  • righe presenti in entrambi i file:

    grep -Fxf file1 file2 > file3

2

Molte risposte già, ma nessuna di loro perfetta IMHO. La risposta di Thanatos lascia alcuni caratteri in più per riga e la risposta di Sorpigal richiede che i file siano ordinati o preordinati, il che potrebbe non essere adeguato in tutte le circostanze.

Penso che il modo migliore di ottenere le linee che sono differenti e nient'altro (niente caratteri aggiuntivi, non ri-ordinazione) è una combinazione di diff, grepe awk(o simili).

Se le righe non contengono "<", una breve riga può essere:

diff urls.txt* | grep "<" | sed 's/< //g'

ma questo rimuoverà ogni istanza di "<" (minore di, spazio) dalle righe, il che non è sempre corretto (ad esempio il codice sorgente). L'opzione più sicura è usare awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

Questa riga di una riga differisce entrambi i file, quindi filtra l'output in stile ed di diff, quindi rimuove il "<" finale aggiunto da diff. Funziona anche se le righe contengono alcuni "<".


1
comm non richiede l'ordinamento (nelle versioni più recenti?) - basta usare --nocheck-order. Lo uso molto quando manipolo csv dalla CLI
ak5

2

Sono sorpreso che nessuno abbia menzionato diff -ydi produrre un output side-by-side , ad esempio:

diff -y file1 file2 > file3

E in file3(diverse linee hanno un simbolo |al centro):

same     same
diff_1 | diff_2

1

Usa l'utilità Diff ed estrai solo le righe che iniziano con <nell'output


0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

Ho provato quasi tutte le risposte in questo thread, ma nessuna era completa. Dopo pochi sentieri sopra uno ha funzionato per me. diff ti darà la differenza ma con alcuni charas speciali indesiderati. dove le linee di differenza effettive iniziano con ">". quindi il passo successivo è quello di grep che le linee iniziano con '>' e seguite rimuovendo lo stesso con sed .


1
Questa è una cattiva idea. Dovresti anche modificare le righe che iniziano con <. Lo vedrai se cambi l'ordine dei file di input. Anche se lo facessi, dovresti omettere grepusando più sed: `diff a1 a2 | sed '/> / s ///' `Questo può ancora interrompere le righe che contengono >o <nella giusta situazione e lascia ancora righe extra che descrivono i numeri di riga. Se si voleva provare questo approccio un modo migliore sarebbe: diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'.
sorpigal

0

È possibile utilizzare diffcon la seguente formattazione di output:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format='', disabilita l'output per file1 se la riga era diversa confronta in file2.
--unchanged-line-format='', disabilita l'output se le righe fossero le stesse.

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.