Comando Unix per trovare linee comuni in due file


179

Sono sicuro di aver trovato una volta un comando unix che poteva stampare le righe comuni da due o più file, qualcuno ne conosce il nome? È stato molto più semplice di diff.


5
Le risposte a questa domanda non sono necessariamente quelle che tutti vorranno, poiché commrichiedono file di input ordinati. Se vuoi solo riga per riga comune, è fantastico. Ma se vuoi quello che chiamerei "anti-diff", commnon fa il lavoro.
Robert P. Goldman,

@ RobertP.Goldman c'è un modo per diventare comuni tra due file quando file1 contiene pattern parziali come pr-123-xy-45e file2 contiene ec11_orop_pr-123-xy-45.gz. Ho bisogno di file3 contenenteec11_orop_pr-123-xy-45.gz
Chandan Choudhury,

Vedi questo per ordinare i file di testo riga per riga
y2k-shubham

Risposte:


216

Il comando che stai cercando è comm. per esempio:-

comm -12 1.sorted.txt 2.sorted.txt

Qui:

-1 : sopprimere la colonna 1 (righe univoche per 1.sorted.txt)

-2 : sopprimere la colonna 2 (righe univoche per 2.sorted.txt)


27
Utilizzo tipico: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK

45
Mentre comm ha bisogno di file ordinati, puoi prendere grep -f file1 file2 per ottenere le linee comuni di entrambi i file.
ferdy

2
@ferdy (ripetere il mio commento dalla tua risposta, poiché la tua è essenzialmente una risposta ripetuta pubblicata come commento) grepfa alcune cose strane che potresti non aspettarti. Nello specifico, tutto in 1.txtverrà interpretato come un'espressione regolare e non come una semplice stringa. Inoltre, qualsiasi riga vuota in 1.txtcorrisponderà a tutte le righe in 2.txt. Quindi grepfunzionerà solo in situazioni molto specifiche. Almeno vorresti usare fgrep(o grep -f) ma la cosa in bianco probabilmente causerà il caos in questo processo.
Christopher Schultz,

11
Vedi la risposta di Ferdy qui sotto, e quella di Christopher Schultz e i miei commenti su di essa. TL; DR - uso . grep -F -x -f file1 file2
Jonathan Leffler,

1
@bapors: ho fornito una domanda e risposta con risposta automatica come Come ottenere l'output dal commcomando in 3 file separati? La risposta era troppo grande per adattarsi comodamente qui.
Jonathan Leffler,

62

Per applicare facilmente il comando comm ai file non ordinati , utilizzare la sostituzione di processo di Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Quindi i file abc e def hanno una riga in comune, quella con "132". Utilizzo di comm su file non ordinati:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

L'ultima riga non ha prodotto output, la riga comune non è stata scoperta.

Ora usa comm su file ordinati, ordinando i file con la sostituzione del processo:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Ora abbiamo la linea 132!


2
così ... sort abc > abc.sorted, sort dev > def.sortede poi comm -12 abc.sorted def.sorted?
Nikana Reklawyks,

1
@NikanaReklawyks E poi ricordati di rimuovere i file temporanei in seguito e di occuparti della pulizia in caso di errore. In molti scenari, la sostituzione del processo sarà anche molto più veloce perché è possibile evitare l'I / O del disco fintanto che i risultati si adattano alla memoria.
Tripleee

29

Per completare il one-liner Perl, ecco il suo awkequivalente:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Questo leggerà tutte le righe file1dall'array arr[]e quindi controllerà ogni riga file2se esiste già all'interno dell'array (es file1.). Le linee trovate verranno stampate nell'ordine in cui appaiono file2. Si noti che il confronto in arrutilizza l'intera riga da file2come indice all'array, quindi riporterà solo corrispondenze esatte su intere righe.


2
QUESTA (!) È la risposta corretta. Nessuno degli altri può essere fatto funzionare generalmente (non ho provato perlquelli, perché). Grazie
mille

1
Preservare l'ordine quando si visualizzano le linee comuni può essere davvero utile in alcuni casi che escluderebbe la comunicazione a causa di ciò.
tuxayo,

1
Nel caso in cui qualcuno voglia fare la stessa cosa in base a una determinata colonna ma non sappia awk, basta sostituire entrambi $ 0 con $ 5, ad esempio per la colonna 5 in modo da ottenere linee condivise in 2 file con le stesse parole nella colonna 5
FatihSarigol,

24

Forse vuoi dire comm?

Confronta i file ordinati FILE1 e FILE2 riga per riga.

Senza opzioni, produce output a tre colonne. La colonna uno contiene righe univoche per FILE1, la colonna due contiene righe univoche per FILE2 e la colonna tre contiene righe comuni a entrambi i file.

Il segreto per trovare queste informazioni sono le pagine informative. Per i programmi GNU, sono molto più dettagliati delle loro pagine man. Prova info coreutilse ti elencherà tutti i piccoli utili programmi di utilità.


19

Mentre

grep -v -f 1.txt 2.txt > 3.txt

ti dà le differenze di due file (ciò che è in 2.txt e non in 1.txt), potresti facilmente fare un

grep -f 1.txt 2.txt > 3.txt

per raccogliere tutte le linee comuni, che dovrebbero fornire una soluzione semplice al tuo problema. Se hai ordinato i file, dovresti commcomunque prenderli . Saluti!


2
grepfa alcune cose strane che potresti non aspettarti. Nello specifico, tutto in 1.txtverrà interpretato come un'espressione regolare e non come una semplice stringa. Inoltre, qualsiasi riga vuota in 1.txtcorrisponderà a tutte le righe in 2.txt. Quindi funzionerà solo in situazioni molto specifiche.
Christopher Schultz,

13
@ChristopherSchultz: è possibile aggiornare questa risposta per funzionare meglio usando le grepnotazioni POSIX , che sono supportate da quelle greptrovate sulla maggior parte delle varianti Unix moderne. Aggiungi -F(o usa fgrep) per sopprimere le espressioni regolari. Aggiungi -x(per l'esatto) per abbinare solo intere righe.
Jonathan Leffler,

Perché dovremmo prendere commper i file ordinati?
Ulysse BN,

2
@UlysseBN commpuò funzionare con file di dimensioni arbitrarie purché siano ordinati perché deve sempre contenere solo tre righe in memoria (suppongo che GNU commsaprebbe anche mantenere un prefisso se le righe sono davvero lunghe). La grepsoluzione deve mantenere in memoria tutte le espressioni di ricerca.
Tripleee,

9

Se i due file non sono ancora ordinati, è possibile utilizzare:

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

e funzionerà, evitando il messaggio di errore comm: file 2 is not in sorted order quando lo si fa comm -12 a.txt b.txt.


Hai ragione, ma essenzialmente si tratta di ripetere un'altra risposta , che in realtà non offre alcun vantaggio. Se decidi di rispondere a una domanda precedente con risposte ben definite e corrette, l'aggiunta di una nuova risposta a fine giornata potrebbe non farti ottenere alcun credito. Se hai alcune nuove informazioni distintive, o sei convinto che le altre risposte siano tutte sbagliate, aggiungi sicuramente una nuova risposta, ma "l'ennesima risposta" fornisce le stesse informazioni di base molto tempo dopo che la domanda è stata generalmente vinta " ti guadagno molto credito.
Jonathan Leffler,

Non ho nemmeno visto questa risposta @JonathanLeffler perché questa parte era alla fine della risposta, mescolata con altri elementi di risposta prima. Mentre l'altra risposta è più precisa, penso che il mio vantaggio sia che per chi desidera una soluzione rapida avranno solo 2 righe da leggere. A volte stiamo cercando una risposta dettagliata, a volte siamo di fretta e una risposta pronta da incollare veloce da leggere va bene.
Basj,

Inoltre non mi importa di credito / rappresentante, non ho pubblicato per questo scopo.
Basj,

1
Si noti inoltre che la sintassi di sostituzione del processo <(command)non è portabile sulla shell POSIX, sebbene funzioni in Bash e in altri.
Tripleee,

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

funziona meglio del commcomando poiché cerca ogni riga di file1in file2dove commsi confronta solo se line nin file1è uguale a line nin file2.
Teriiehina,

1
@teriiehina: No; commnon confronta semplicemente la riga N in file1 con la riga N in file2. Può perfettamente gestire una serie di linee inserite in entrambi i file (che equivale a eliminare una serie di linee dall'altro file, ovviamente). Richiede semplicemente che gli input siano in ordine.
Jonathan Leffler,

Meglio delle commrisposte se si vuole mantenere l'ordine. Meglio che awkrispondere se non si vogliono duplicati.
tuxayo,



3

Su una versione limitata di Linux (come un QNAP (nas) su cui stavo lavorando):

  • comm non esisteva
  • grep -f file1 file2può causare alcuni problemi, come affermato da @ChristopherSchultz e l'utilizzo è grep -F -f file1 file2stato molto lento (più di 5 minuti - non finito - oltre 2-3 secondi con il metodo seguente su file di oltre 20 MB)

Quindi ecco cosa ho fatto:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Se files.same.sorteddeve essere stato nello stesso ordine di quelli originali, allora aggiungi questa riga per lo stesso ordine di file1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

oppure, per lo stesso ordine di file2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

Solo per riferimento se qualcuno sta ancora cercando come farlo per più file, vedere la risposta collegata alla ricerca di linee corrispondenti in più file.


Combinando queste due risposte ( ans1 e ans2 ), penso che tu possa ottenere il risultato di cui hai bisogno senza ordinare i file:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Basta salvarlo, dargli i diritti di esecuzione ( chmod +x compareFiles.sh) ed eseguirlo. Prenderà tutti i file presenti nella directory di lavoro corrente e farà un confronto tutto-contro-tutti lasciando nel file "matching_lines" il risultato.

Cose da migliorare:

  • Salta le directory
  • Evita di confrontare tutti i file due volte (file1 vs file2 e file2 vs file1).
  • Forse aggiungi il numero di riga accanto alla stringa corrispondente

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Questo dovrebbe farlo.


1
Probabilmente dovresti usare rm -f file3.txtse hai intenzione di eliminare il file; che non segnalerà alcun errore se il file non esiste. OTOH, non sarebbe necessario se il tuo script facesse semplicemente eco all'output standard, lasciando all'utente lo script la scelta della destinazione dell'output. Alla fine, probabilmente vorrai usare $1e $2(argomenti della riga di comando) invece di nomi di file fissi ( file1.oute file2.out). Questo lascia l'algoritmo: sarà lento. Leggerà file2.outuna volta per ogni riga in file1.out. Sarà lento se i file sono grandi (diciamo più kilobyte).
Jonathan Leffler,

Sebbene ciò possa funzionare nominalmente se si dispone di input che non contengono metacaratteri della shell (suggerimento: vedere quali avvertimenti si ottengono da shellcheck.net ), questo approccio ingenuo è terribilmente inefficiente. Uno strumento come quello grep -Fche legge un file in memoria e quindi esegue un singolo passaggio sull'altro evita ripetutamente il looping su entrambi i file di input.
Tripleee
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.