Ich habe zwei große Dateien (Sätze von Dateinamen). Etwa 30.000 Zeilen in jeder Datei. Ich versuche einen schnellen Weg zu finden, um Zeilen in Datei1 zu finden, die in Datei2 nicht vorhanden sind.
Wenn dies beispielsweise Datei1 ist:
line1
line2
line3
Und das ist Datei2:
line1
line4
line5
Dann sollte mein Ergebnis / meine Ausgabe sein:
line2
line3
Das funktioniert:
grep -v -f file2 file1
Aber es ist sehr, sehr langsam, wenn es für meine großen Dateien verwendet wird.
Ich vermute, dass es einen guten Weg gibt, dies mit diff () zu tun, aber die Ausgabe sollte nur die Zeilen sein, sonst nichts, und ich kann keinen Schalter dafür finden.
Kann mir jemand helfen, einen schnellen Weg zu finden, indem ich Bash- und grundlegende Linux-Binärdateien verwende?
EDIT: Um meine eigene Frage zu beantworten, ist dies der beste Weg, den ich bisher mit diff () gefunden habe:
diff file2 file1 | grep '^>' | sed 's/^>\ //'
Sicherlich muss es einen besseren Weg geben?
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
cat file1 file2 file2 | sort | uniq --unique
siehe meine Antwort unten.Antworten:
Sie können dies erreichen, indem Sie die Formatierung der alten / neuen / unveränderten Zeilen in der GNU-
diff
Ausgabe steuern :Die Eingabedateien sollten sortiert sein, damit dies funktioniert. Mit
bash
(undzsh
) können Sie direkt mit der Prozessersetzung sortieren<( )
:In den obigen Abschnitten werden neue und unveränderte Zeilen unterdrückt, sodass nur geänderte (in Ihrem Fall entfernte Zeilen) ausgegeben werden. Sie können auch ein paar verwenden
diff
Optionen , die anderen Lösungen nicht bieten, wie-i
Fall zu ignorieren, oder verschiedene Leerzeichen Optionen (-E
,-b
,-v
usw.) für weniger strenges Matching.Erläuterung
Die Optionen
--new-line-format
,--old-line-format
und--unchanged-line-format
lassen Sie die Art und Weise steuern ,diff
formatiert die Unterschiede ähnlichprintf
Formatbezeichner. Diese Optionen formatieren neue (hinzugefügte), alte (entfernte) bzw. unveränderte Zeilen. Wenn Sie "leer" setzen, wird die Ausgabe dieser Art von Zeile verhindert.Wenn Sie mit dem einheitlichen Diff- Format vertraut sind , können Sie es teilweise neu erstellen mit:
Der
%L
Bezeichner ist die betreffende Zeile, und wir stellen jeweils "+" "-" oder "" vorandiff -u
(beachten Sie, dass nur Unterschiede ausgegeben werden---
+++
und die@@
Zeilen und oben bei jeder gruppierten Änderung fehlen ). Sie können dies auch verwenden, um andere nützliche Dinge zu tun, z. B. die Nummerierung jeder Zeile mit%dn
.Die
diff
Methode (zusammen mit anderen Vorschlägencomm
undjoin
) erzeugt nur die erwartete Ausgabe mit sortierter Eingabe, obwohl Sie sie<(sort ...)
zum Sortieren verwenden können. Hier ist ein einfachesawk
(nawk) Skript (inspiriert von den in Konsoleboxs Antwort verknüpften Skripten), das willkürlich geordnete Eingabedateien akzeptiert und die fehlenden Zeilen in der Reihenfolge ausgibt, in der sie in Datei1 vorkommen.Dies speichert den gesamten Inhalt von Datei1 Zeile für Zeile in einem Zeilennummern-indizierten Array
ll1[]
und den gesamten Inhalt von Datei2 Zeile für Zeile in einem Zeileninhalt-indizierten assoziativen Arrayss2[]
. Nachdem beide Dateien gelesen wurden, wiederholen Sie den Vorgangll1
und verwenden Sie denin
Operator, um festzustellen, ob die Zeile in Datei1 in Datei2 vorhanden ist. (Dies hat eine andere Ausgabe als diediff
Methode, wenn Duplikate vorhanden sind.)Für den Fall, dass die Dateien so groß sind, dass das Speichern beider Dateien ein Speicherproblem verursacht, können Sie die CPU gegen Speicher tauschen, indem Sie nur Datei1 speichern und Übereinstimmungen während des Lesens von Datei2 löschen.
Oben wird der gesamte Inhalt von Datei1 in zwei Arrays gespeichert, von denen eines nach Zeilennummer
ll1[]
und eines nach Zeileninhalt indiziert istss1[]
. Beim Lesen von Datei2 wird dann jede übereinstimmende Zeile ausll1[]
und gelöschtss1[]
. Am Ende werden die verbleibenden Zeilen aus Datei1 ausgegeben, wobei die ursprüngliche Reihenfolge beibehalten wird.In diesem Fall können Sie mit dem angegebenen Problem auch mithilfe von GNU (Filterung ist eine GNU-Erweiterung) teilen und siegen
split
, wiederholte Läufe mit Blöcken von Datei1 ausführen und Datei2 jedes Mal vollständig lesen:Beachten Sie die Verwendung und Platzierung der
-
Bedeutungstdin
in dergawk
Befehlszeile. Dies wird vonsplit
aus Datei1 in Blöcken von 20000 Zeilen pro Aufruf bereitgestellt .Für Benutzer auf nicht-GNU - Systemen gibt an Sicherheit grenzender Wahrscheinlichkeit ist ein GNU coreutils Paket , das Sie erhalten, auf OSX auch im Rahmen der Apple - Xcode - Tool , das GNU bietet
diff
,awk
allerdings nur ein POSIX / BSDsplit
eher als eine GNU - Version.quelle
diff
: Im Allgemeinen sind die Eingabedateien unterschiedlich, 1 wirddiff
in diesem Fall von zurückgegeben. Betrachten Sie es als Bonus ;-) Wenn Sie in einem Shell-Skript testen, werden 0 und 1 Exit-Codes erwartet, 2 weist auf ein Problem hin.man diff
. Vielen Dank!Der Befehl comm (kurz für "common") kann nützlich sein
comm - compare two sorted files line by line
Die
man
Datei ist dafür eigentlich gut lesbar.quelle
comm
hat auch eine Option, um zu überprüfen, ob die Eingabe sortiert ist--check-order
(was anscheinend trotzdem der Fall ist, aber diese Option führt zu einem Fehler, anstatt fortzufahren). Aber um die Dateien zu sortieren, machen Sie einfach:com -23 <(sort file1) <(sort file2)
und so weitercomm
als würde sie überhaupt nicht funktionieren. Ich habe eine Weile gebraucht, um herauszufinden, dass es um die Zeilenenden geht: Selbst Zeilen, die identisch aussehen, werden als unterschiedlich angesehen, wenn sie unterschiedliche Zeilenenden haben. Der Befehldos2unix
kann verwendet werden, um die CRLF-Zeilenenden nur in LF zu konvertieren.Wie konsolebox vorgeschlagen hat, ist die Poster-Grep-Lösung
funktioniert wirklich gut (schnell), wenn Sie einfach die
-F
Option hinzufügen , die Muster als feste Zeichenfolgen anstelle von regulären Ausdrücken zu behandeln. Ich habe dies anhand von ~ 1000 Zeilendateilisten überprüft, die ich vergleichen musste. Mit-F
es dauerte 0,031 s (real), während es ohne 2,278 s (real) dauerte, wenn die grep-Ausgabe auf umgeleitet wurdewc -l
.Diese Tests umfassten auch den
-x
Schalter, der Teil der Lösung ist, um die vollständige Genauigkeit in Fällen sicherzustellen, in denen Datei2 Zeilen enthält, die mit einem Teil, aber nicht allen einer oder mehreren Zeilen in Datei1 übereinstimmen.Eine Lösung, bei der die Eingaben nicht sortiert werden müssen, ist schnell und flexibel (Groß- / Kleinschreibung usw.):
Dies funktioniert nicht mit allen Versionen von grep, z. B. schlägt es unter macOS fehl, wo eine Zeile in Datei 1 als nicht in Datei 2 vorhanden angezeigt wird, obwohl dies der Fall ist, wenn sie mit einer anderen Zeile übereinstimmt, die eine Teilzeichenfolge davon ist . Alternativ können Sie GNU grep unter macOS installieren , um diese Lösung zu verwenden.
quelle
-F
damit lässt sich nicht gut skalieren.file2
.-x
Option verwendet jedoch anscheinend mehr Speicher. Mitfile2
180M Wörtern von 6-10 Bytes wurde mein ProzessKilled
auf einem 32 GB RAM-Computer ausgeführt ...Was ist die Geschwindigkeit als sort und diff?
quelle
Wenn Sie wenig „fancy Werkzeuge“ sind, zB in einem gewissen minimalen Linux - Distribution, gibt es eine Lösung mit nur
cat
,sort
unduniq
:Prüfung:
Dies ist auch relativ schnell im Vergleich zu
grep
.quelle
--unique
Option nicht. Sie sollten in der Lage sein, die standardisierte POSIX-Option für diese zu verwenden:| uniq -u
seq 1 1 7
erstellt Zahlen von 1 mit Inkrement 1 bis 7, dh 1 2 3 4 5 6 7. Und genau da ist Ihre 2!Das
-t
stellt sicher, dass die gesamte Zeile verglichen wird, wenn Sie in einigen Zeilen ein Leerzeichen hatten.quelle
comm
,join
erfordern beide Eingangsleitungen auf dem Feld sortiert werden Sie die auf Join - Operation durchführen.Sie können Python verwenden:
quelle
Verwenden
combine
vonmoreutils
Paket, ein Dienstprogramm , das Satz Stützennot
,and
,or
,xor
Operationendh gib mir Zeilen, die in Datei1, aber nicht in Datei2 sind
ODER geben Sie mir Zeilen in Datei1 minus Zeilen in Datei2
Hinweis:
combine
Sortiert und findet eindeutige Zeilen in beiden Dateien, bevor eine Operation ausgeführt wird, diesdiff
jedoch nicht. Sie können also Unterschiede zwischen der Ausgabe vondiff
und feststellencombine
.Tatsächlich sagen Sie also
Suchen Sie unterschiedliche Zeilen in Datei1 und Datei2 und geben Sie mir dann Zeilen in Datei1 minus Zeilen in Datei2
Nach meiner Erfahrung ist es viel schneller als andere Optionen
quelle
Die Verwendung von fgrep oder das Hinzufügen der Option -F zu grep könnte helfen. Für schnellere Berechnungen können Sie jedoch Awk verwenden.
Sie können eine dieser Awk-Methoden ausprobieren:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
quelle
Normalerweise verwende ich das
--suppress-common-lines
Flag. Beachten Sie jedoch, dass dies nur funktioniert, wenn Sie es im Side-by-Side-Format ausführen.diff -y --suppress-common-lines file1.txt file2.txt
quelle
Ich fand, dass für mich die Verwendung einer normalen if- und for-Schleifenanweisung perfekt funktionierte.
quelle
grep
Ergebnisse auf mehrere Wörter erweitert wird oder wenn einer Ihrerfile2
Einträge von der Shell als Glob behandelt werden kann.