Unix-Befehl zum Suchen von Zeilen, die in zwei Dateien gemeinsam sind

178

Ich bin sicher, ich habe einmal einen Unix-Befehl gefunden, der die gemeinsamen Zeilen aus zwei oder mehr Dateien drucken kann. Kennt jemand seinen Namen? Es war viel einfacher als diff.

zu viel php
quelle
5
Die Antworten auf diese Frage sind nicht unbedingt das, was sich jeder wünscht, da commsortierte Eingabedateien erforderlich sind. Wenn Sie nur Zeile für Zeile gemeinsam möchten, ist es großartig. Aber wenn Sie wollen, was ich "Anti-Diff" nennen würde, commmachen Sie den Job nicht.
Robert P. Goldman
@ RobertP.Goldman gibt es eine Möglichkeit, zwei Dateien gemeinsam zu nutzen, wenn Datei1 Teilmuster wie pr-123-xy-45und Datei2 enthält ec11_orop_pr-123-xy-45.gz. Ich brauche Datei3 mitec11_orop_pr-123-xy-45.gz
Chandan Choudhury
Siehe dies zum zeilenweisen Sortieren von Textdateien
y2k-shubham

Antworten:

216

Der Befehl, den Sie suchen, ist comm. z.B:-

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

Hier:

-1 : Spalte 1 unterdrücken (Zeilen eindeutig für 1.sorted.txt)

-2 : Spalte 2 unterdrücken (Zeilen eindeutig für 2.sorted.txt)

Jonathan Leffler
quelle
27
Typische Verwendung: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK
45
Während comm sortierte Dateien benötigt, können Sie grep -f file1 file2 verwenden, um die gemeinsamen Zeilen beider Dateien abzurufen.
Ferdy
2
@ferdy (Wiederholen meines Kommentars aus Ihrer Antwort, da Ihre Antwort im Wesentlichen eine wiederholte Antwort ist, die als Kommentar veröffentlicht wurde) grepmacht einige seltsame Dinge, die Sie möglicherweise nicht erwarten. Insbesondere wird alles in 1.txtals regulärer Ausdruck und nicht als einfache Zeichenfolge interpretiert. Außerdem stimmt jede leere Zeile in 1.txtmit allen Zeilen in überein 2.txt. Funktioniert also grepnur in ganz bestimmten Situationen. Sie möchten zumindest fgrep(oder grep -f) verwenden, aber die leere Zeile wird wahrscheinlich Chaos in diesem Prozess anrichten.
Christopher Schultz
11
Siehe ferdy ‚s Antwort unten, und Christopher Schultz ‘ s und meine Kommentare dazu. TL; DR - Verwendung grep -F -x -f file1 file2.
Jonathan Leffler
1
@bapors: Ich habe eine selbst beantwortete Frage und Antwort gegeben, wie man die Ausgabe des commBefehls in 3 separate Dateien bringt. Die Antwort war viel zu groß, um hier bequem zu passen.
Jonathan Leffler
61

Verwenden Sie die Prozessersetzung von Bash, um den Befehl comm einfach auf unsortierte Dateien anzuwenden :

$ 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

Die Dateien abc und def haben also eine Zeile gemeinsam, die mit "132". Verwenden von comm für unsortierte Dateien:

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

Die letzte Zeile erzeugte keine Ausgabe, die gemeinsame Zeile wurde nicht entdeckt.

Verwenden Sie jetzt comm für sortierte Dateien und sortieren Sie die Dateien mit Prozessersetzung:

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

Jetzt haben wir die Linie 132!

Stephan Wehner
quelle
2
so ... sort abc > abc.sorted, sort dev > def.sortedund dann comm -12 abc.sorted def.sorted?
Nikana Reklawyks
1
@NikanaReklawyks Und denken Sie daran, die temporären Dateien anschließend zu entfernen und im Falle eines Fehlers mit der Bereinigung fertig zu werden. In vielen Szenarien ist die Prozessersetzung auch viel schneller, da Sie die Festplatten-E / A vermeiden können, solange die Ergebnisse in den Speicher passen.
Tripleee
29

Als Ergänzung zum Perl-Einzeiler ist hier das awkÄquivalent:

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

Dadurch werden alle Zeilen file1in das Array eingelesen arr[]und anschließend für jede Zeile überprüft, file2ob sie bereits im Array vorhanden ist (dh file1). Die gefundenen Zeilen werden in der Reihenfolge gedruckt, in der sie angezeigt werden file2. Beachten Sie, dass der Vergleich in arrdie gesamte Zeile von file2als Index für das Array verwendet, sodass nur genaue Übereinstimmungen für ganze Zeilen gemeldet werden.

Tatjana Heuser
quelle
2
DIESES (!) Ist die richtige Antwort. Keiner der anderen kann dazu gebracht werden, allgemein zu arbeiten (ich habe die nicht ausprobiert perl, weil). Vielen Dank, Frau
Entonio
1
Das Beibehalten der Reihenfolge beim Anzeigen der gemeinsamen Zeilen kann in einigen Fällen sehr nützlich sein, in denen die Kommunikation aus diesem Grund ausgeschlossen ist.
Smoking
1
Wenn jemand dasselbe basierend auf einer bestimmten Spalte tun möchte, aber awk nicht kennt, ersetzen Sie einfach beide $ 0 durch $ 5, zum Beispiel für Spalte 5, damit Sie Zeilen in 2 Dateien mit denselben Wörtern in Spalte 5 teilen
FatihSarigol
24

Vielleicht meinst du comm?

Vergleichen Sie die sortierten Dateien FILE1 und FILE2 zeilenweise.

Erstellen Sie ohne Optionen eine dreispaltige Ausgabe. Spalte eins enthält Zeilen, die für FILE1 eindeutig sind, Spalte zwei enthält Zeilen, die für FILE2 eindeutig sind, und Spalte drei enthält Zeilen, die beiden Dateien gemeinsam sind.

Das Geheimnis beim Auffinden dieser Informationen sind die Infoseiten. Für GNU-Programme sind sie viel detaillierter als ihre Manpages. Versuchen Sie es info coreutilsund es werden Ihnen alle kleinen nützlichen Hilfsprogramme aufgelistet.

Johannes Schaub - litb
quelle
19

Während

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

gibt Ihnen die Unterschiede von zwei Dateien (was in 2.txt und nicht in 1.txt ist), könnten Sie leicht eine tun

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

alle gemeinsamen Zeilen zu sammeln, die eine einfache Lösung für Ihr Problem bieten sollten. Wenn Sie Dateien sortiert haben, sollten Sie commtrotzdem nehmen . Grüße!

Ferdy
quelle
2
grepmacht einige seltsame Dinge, die Sie vielleicht nicht erwarten. Insbesondere wird alles in 1.txtals regulärer Ausdruck und nicht als einfache Zeichenfolge interpretiert. Außerdem stimmt jede leere Zeile in 1.txtmit allen Zeilen in überein 2.txt. Dies funktioniert also nur in ganz bestimmten Situationen.
Christopher Schultz
13
@ChristopherSchultz: Es ist möglich, diese Antwort zu aktualisieren, um mit POSIX- grepNotationen besser zu funktionieren , die von den grepmeisten modernen Unix-Varianten unterstützt werden. Hinzufügen -F(oder Verwenden fgrep), um reguläre Ausdrücke zu unterdrücken. Fügen Sie -x(genau) hinzu, um nur ganze Zeilen abzugleichen.
Jonathan Leffler
Warum sollten wir commfür sortierte Dateien nehmen?
Ulysse BN
2
@UlysseBN commkann mit beliebig großen Dateien arbeiten, solange sie sortiert sind, da immer nur drei Zeilen im Speicher gespeichert werden müssen (ich vermute, GNU commwürde sogar wissen, dass nur ein Präfix beibehalten werden muss, wenn die Zeilen wirklich lang sind). Die grepLösung muss alle Suchausdrücke im Speicher behalten.
Tripleee
8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2
user2592005
quelle
das funktioniert besser als der commBefehl , wie es jede Zeile sucht file1in file2denen commnur vergleichen , wenn Zeile nin file1mit der Leitung gleich nin file2.
Teriiehina
1
@teriiehina: Nein; commvergleicht nicht einfach Zeile N in Datei1 mit Zeile N in Datei2. Es kann eine Reihe von Zeilen, die in eine der beiden Dateien eingefügt wurden, perfekt verwalten (was natürlich dem Löschen einer Reihe von Zeilen aus der anderen Datei entspricht). Es ist lediglich erforderlich, dass die Eingaben in sortierter Reihenfolge vorliegen.
Jonathan Leffler
Besser als commAntworten, wenn man die Reihenfolge halten will. Besser als zu awkantworten, wenn man keine Duplikate will.
Smoking
Eine Erklärung finden Sie hier: stackoverflow.com/questions/17552789/…
Chris Koknat
8

Wenn die beiden Dateien noch nicht sortiert sind, können Sie Folgendes verwenden:

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

und es wird funktionieren, um die Fehlermeldung zu vermeiden , comm: file 2 is not in sorted order wenn dabei comm -12 a.txt b.txt.

Basj
quelle
Sie haben Recht, aber dies wiederholt im Wesentlichen eine andere Antwort , die wirklich keinen Nutzen bringt. Wenn Sie sich entscheiden, eine ältere Frage zu beantworten, die gut etablierte und korrekte Antworten enthält, erhalten Sie möglicherweise keine Gutschrift, wenn Sie spät am Tag eine neue Antwort hinzufügen. Wenn Sie einige unverwechselbare neue Informationen haben oder davon überzeugt sind, dass die anderen Antworten alle falsch sind, fügen Sie auf jeden Fall eine neue Antwort hinzu, aber "noch eine Antwort", die die gleichen grundlegenden Informationen lange nach dem Stellen der Frage liefert, ist normalerweise gewonnen. " Sie verdienen nicht viel Kredit.
Jonathan Leffler
Ich habe diese Antwort @JonathanLeffler nicht einmal gesehen, weil dieser Teil ganz am Ende der Antwort stand, gemischt mit anderen Elementen der Antwort zuvor. Während die andere Antwort präziser ist, denke ich, dass der Vorteil von jemandem, der eine schnelle Lösung wünscht, nur 2 Zeilen zum Lesen hat. Manchmal suchen wir nach detaillierten Antworten und manchmal haben wir es eilig und eine schnell zu lesende, einfügbare Antwort ist in Ordnung.
Basj
Außerdem ist mir Kredit / Repräsentant egal, ich habe nicht für diesen Zweck gepostet.
Basj
1
Beachten Sie auch, dass die Syntax der Prozessersetzung <(command)nicht auf die POSIX-Shell portierbar ist, obwohl sie in Bash und einigen anderen funktioniert.
Tripleee
5
awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2
RS John
quelle
3

Auf einer eingeschränkten Linux-Version (wie einem QNAP (nas), an dem ich gearbeitet habe):

  • comm existierte nicht
  • grep -f file1 file2kann einige Probleme verursachen, wie von @ChristopherSchultz gesagt, und die Verwendung grep -F -f file1 file2war sehr langsam (mehr als 5 Minuten - nicht beendet - über 2-3 Sekunden mit der folgenden Methode bei Dateien über 20 MB)

Also hier ist was ich getan habe:

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

Wenn files.same.sortedes in derselben Reihenfolge wie die ursprünglichen gewesen sein soll, fügen Sie diese Zeile in derselben Reihenfolge wie Datei1 hinzu:

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

oder für dieselbe Reihenfolge wie Datei2:

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

Nur als Referenz, wenn noch jemand nach Möglichkeiten sucht, dies für mehrere Dateien zu tun, lesen Sie die verknüpfte Antwort auf Suchen nach übereinstimmenden Zeilen in vielen Dateien.


Wenn Sie diese beiden Antworten ( ans1 und ans2 ) kombinieren , können Sie das gewünschte Ergebnis erzielen , ohne die Dateien zu sortieren:

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

Speichern Sie es einfach, geben Sie ihm Ausführungsrechte ( chmod +x compareFiles.sh) und führen Sie es aus. Es werden alle im aktuellen Arbeitsverzeichnis vorhandenen Dateien verwendet und ein All-vs-All-Vergleich durchgeführt, wobei das Ergebnis in der Datei "Matching_lines" verbleibt.

Dinge, die verbessert werden müssen:

  • Verzeichnisse überspringen
  • Vermeiden Sie es, alle Dateien zweimal zu vergleichen (Datei1 gegen Datei2 und Datei2 gegen Datei1).
  • Fügen Sie möglicherweise die Zeilennummer neben der passenden Zeichenfolge hinzu
Akarpovsky
quelle
-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

Das sollte es tun.

Alan Joseph
quelle
1
Sie sollten wahrscheinlich verwenden, rm -f file3.txtwenn Sie die Datei löschen möchten. Das meldet keinen Fehler, wenn die Datei nicht existiert. OTOH, es wäre nicht notwendig, wenn Ihr Skript einfach auf die Standardausgabe zurückgreift und der Benutzer des Skripts auswählen kann, wohin die Ausgabe gehen soll. Letztendlich möchten Sie wahrscheinlich $1und $2(Befehlszeilenargumente) anstelle fester Dateinamen ( file1.outund file2.out) verwenden. Damit bleibt der Algorithmus: Es wird langsam. Es wird file2.outeinmal für jede Zeile in gelesen file1.out. Es wird langsam sein, wenn die Dateien groß sind (sagen wir mehrere Kilobyte).
Jonathan Leffler
Während dies nominell funktionieren kann, wenn Sie Eingaben haben, die keine Shell-Metazeichen enthalten (Hinweis: Sehen Sie, welche Warnungen Sie von shellcheck.net erhalten ), ist dieser naive Ansatz schrecklich ineffizient. Ein Tool wie grep -Fdieses liest eine Datei in den Speicher und führt dann einen einzigen Durchlauf über die andere aus, um zu vermeiden, dass beide Eingabedateien wiederholt durchlaufen werden.
Tripleee