Confronto tra due file nel terminale Linux


168

Esistono due file chiamati "a.txt" e "b.txt" hanno entrambi un elenco di parole. Ora voglio controllare quali parole sono extra in "a.txt" e non in "b.txt" .

Ho bisogno di un algoritmo efficiente in quanto ho bisogno di confrontare due dizionari.


27
diff a.txt b.txtnon è abbastanza?
ThanksForAllTheFish,

Le parole possono capitare più volte in ciascun file? Puoi ordinare i file?
Basile Starynkevitch,

ho solo bisogno di quelle parole che non sono presenti in "b.txt" e sono presenti in a.txt
Ali Imran,

Risposte:


343

se hai installato vim, prova questo:

vimdiff file1 file2

o

vim -d file1 file2

lo troverai fantastico.inserisci qui la descrizione dell'immagine


9
sicuramente fantastico, buono nel design e facile da scoprire le differenze. Ohmygod
Zen,

1
La tua risposta è fantastica, ma il mio insegnante mi ha richiesto di non utilizzare alcuna funzione di libreria: P
Ali Imran,

1
Che grande strumento! Questo è immensamente utile.
user1205577

1
Quali sono i significati di quei colori?
zygimantus,

1
I codici colorati indicano che sono diversi in due file. @zygimantus
Fengya Li

73

Ordinali e usa comm:

comm -23 <(sort a.txt) <(sort b.txt)

commconfronta i file di input (ordinati) e, per impostazione predefinita, genera tre colonne: righe univoche per a, righe univoche per b e righe presenti in entrambi. Specificando -1, -2e / o -3è possibile sopprimere l'output corrispondente. Pertanto comm -23 a belenca solo le voci che sono uniche per a. Uso la <(...)sintassi per ordinare i file al volo, se sono già ordinati non è necessario.


Ho aggiunto la mia risposta usando solo i comandi grep, per favore dimmi se è più efficiente?
Ali Imran,

3
@AliImran, commè più efficiente perché esegue il lavoro in una sola esecuzione, senza memorizzare l'intero file in memoria. Dal momento che stai usando dizionari che molto probabilmente sono già ordinati, non hai nemmeno bisogno di sortloro. L'uso grep -f file1 file2d'altro canto caricherà l'intero file1in memoria e confronterà ogni riga file2con tutte quelle voci, il che è molto meno efficiente. È utile soprattutto per piccoli, non ordinati-f file1 .
Anders Johansson,

1
Grazie @AndersJohansson per aver condiviso il comando "comm". È davvero elegante. Spesso devo fare unioni esterne tra i file e questo fa il trucco.
blispr,

Presta attenzione al nuovo carattere di linea ... Ho appena scoperto che \nsarà incluso anche per fare il confronto.
Bin

31

Prova sdiff( man sdiff)

sdiff -s file1 file2

28

È possibile utilizzare lo diffstrumento in Linux per confrontare due file. È possibile utilizzare le opzioni --changed-group-format e --unchanged-group-format per filtrare i dati richiesti.

Le seguenti tre opzioni possono essere utilizzate per selezionare il gruppo pertinente per ciascuna opzione:

  • '% <' ottiene linee da FILE1

  • '%>' ottiene linee da FILE2

  • '' (stringa vuota) per rimuovere le righe da entrambi i file.

Ad esempio: diff --changed-group-format = "% <" --unchanged-group-format = "" file1.txt file2.txt

[root@vmoracle11 tmp]# cat file1.txt 
test one
test two
test three
test four
test eight
[root@vmoracle11 tmp]# cat file2.txt 
test one
test three
test nine
[root@vmoracle11 tmp]# diff --changed-group-format='%<' --unchanged-group-format='' file1.txt file2.txt 
test two
test four
test eight

27

Se preferisci lo stile di output diff git diff, puoi usarlo con il --no-indexflag per confrontare i file che non sono in un repository git:

git diff --no-index a.txt b.txt

Usando un paio di file con circa 200k stringhe di nomi di file in ciascuno, ho confrontato (con il timecomando integrato ) questo approccio rispetto ad alcune delle altre risposte qui:

git diff --no-index a.txt b.txt
# ~1.2s

comm -23 <(sort a.txt) <(sort b.txt)
# ~0.2s

diff a.txt b.txt
# ~2.6s

sdiff a.txt b.txt
# ~2.7s

vimdiff a.txt b.txt
# ~3.2s

commsembra essere di gran lunga il più veloce, mentre git diff --no-indexsembra essere l'approccio più veloce per l'output in stile diff.


Aggiornamento 25-03-2018 È possibile omettere il --no-indexflag a meno che non ci si trovi all'interno di un repository git e si desideri confrontare i file non tracciati all'interno di quel repository. Dalle pagine man :

Questo modulo serve per confrontare i due percorsi indicati sul filesystem. È possibile omettere l'opzione --no-index quando si esegue il comando in un albero di lavoro controllato da Git e almeno uno dei punti dei percorsi all'esterno dell'albero di lavoro o quando si esegue il comando all'esterno di un albero di lavoro controllato da Git.




4

Usa comm -13 (richiede file ordinati) :

$ cat file1
one
two
three

$ cat file2
one
two
three
four

$ comm -13 <(sort file1) <(sort file2)
four

1

Ecco la mia soluzione per questo:

mkdir temp
mkdir results
cp /usr/share/dict/american-english ~/temp/american-english-dictionary
cp /usr/share/dict/british-english ~/temp/british-english-dictionary
cat ~/temp/american-english-dictionary | wc -l > ~/results/count-american-english-dictionary
cat ~/temp/british-english-dictionary | wc -l > ~/results/count-british-english-dictionary
grep -Fxf ~/temp/american-english-dictionary ~/temp/british-english-dictionary > ~/results/common-english
grep -Fxvf ~/results/common-english ~/temp/american-english-dictionary > ~/results/unique-american-english
grep -Fxvf ~/results/common-english ~/temp/british-english-dictionary > ~/results/unique-british-english

2
Hai provato una delle altre soluzioni? Una di queste soluzioni ti è stata utile? La tua domanda è abbastanza generica da attirare molti utenti, ma la tua risposta è più specifica per i miei gusti ... Per il mio caso particolare è sdiff -s file1 file2stato utile.
Metafaniel,

@Metafaniel la mia soluzione non usa il comando sdiff. Usa solo i comandi integrati di Linux per risolvere il problema.
Ali Imran,

-1

Usando awk per questo. File di test:

$ cat a.txt
one
two
three
four
four
$ cat b.txt
three
two
one

Il awk:

$ awk '
NR==FNR {                    # process b.txt  or the first file
    seen[$0]                 # hash words to hash seen
    next                     # next word in b.txt
}                            # process a.txt  or all files after the first
!($0 in seen)' b.txt a.txt   # if word is not hashed to seen, output it

I duplicati vengono emessi:

four
four

Per evitare duplicati, aggiungi ogni parola appena incontrata in a.txt seenall'hash:

$ awk '
NR==FNR {
    seen[$0]
    next
}
!($0 in seen) {              # if word is not hashed to seen
    seen[$0]                 # hash unseen a.txt words to seen to avoid duplicates 
    print                    # and output it
}' b.txt a.txt

Produzione:

four

Se gli elenchi di parole sono separati da virgola, come:

$ cat a.txt
four,four,three,three,two,one
five,six
$ cat b.txt
one,two,three

devi fare un paio di giri extra ( forloop):

awk -F, '                    # comma-separated input
NR==FNR {
    for(i=1;i<=NF;i++)       # loop all comma-separated fields
        seen[$i]
    next
}
{
    for(i=1;i<=NF;i++)
        if(!($i in seen)) {
             seen[$i]        # this time we buffer output (below):
             buffer=buffer (buffer==""?"":",") $i
        }
    if(buffer!="") {         # output unempty buffers after each record in a.txt
        print buffer
        buffer=""
    }
}' b.txt a.txt

Uscita questa volta:

four
five,six
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.