bash, Linux: Set Unterschied zwischen zwei Textdateien
-
22-09-2019 - |
Frage
Ich habe zwei Dateien A
-nodes_to_delete
und B
-nodes_to_keep
. Jede Datei hat eine viele Zeilen mit numerischen IDs.
Ich mag die Liste der numerischen IDs haben, die in nodes_to_delete
sind, aber nicht in nodes_to_keep
, z.B.
.
es in einer PostgreSQL-Datenbank zu tun, ist unzumutbar langsam. Jede ordentliche Weise es in der Bash mit Linux CLI-Tool zu tun?
UPDATE: Dies scheint ein Pythonic Job zu sein, aber die Dateien sind wirklich, wirklich groß. Ich habe einige ähnliche Probleme mit uniq
, sort
und einige Mengenlehre Techniken gelöst. Das war vor etwa zwei oder drei Größenordnungen schneller als die Datenbank-Äquivalente.
Lösung
comm Befehl tut das.
Andere Tipps
Jemand hat mir gezeigt, wie vor genau dieses in sh ein paar Monate zu tun, und dann konnte ich es nicht für eine Weile finden ... und während ich auf der Suche stießen zufällig auf Ihre Frage. Hier ist sie:
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
Mit comm
-. Es vergleicht zwei sortierte Dateien Zeile für Zeile
Die kurze Antwort auf Ihre Frage
Dieser Befehl Linien eindeutig deleteNodes zurück, aber nicht Linien in keepNodes.
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
Beispiel-Setup
Lassen Sie uns Erstellen Sie die Dateien mit dem Namen keepNodes
und deleteNodes
, und sie als unsortierter Eingang für den comm
Befehl.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
Standardmäßig läuft comm ohne Argumente druckt 3 Spalten mit diesem Layout:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
Mit unserer Beispieldatei oben, laufen comm ohne Argumente. Beachten Sie die drei Spalten.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
Unterdrückspaltenausgang
Suppress Spalte 1, 2 oder 3 mit -N; Beachten Sie, dass, wenn eine Spalte ausgeblendet ist, das Leerzeichen schrumpft auf.
$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob
Sortierung ist wichtig!
Wenn Sie comm ausführen, ohne zuerst die Datei zu sortieren, es nicht ordnungsgemäß mit einer Meldung über die Datei sortiert ist es nicht.
comm: file 1 is not in sorted order
comm
speziell für diese Art von Anwendungsfall entwickelt wurde, aber es erfordert sortierten Eingang.
awk
ist wohl ein besseres Werkzeug für diese, da es ziemlich gerade ist vorwärts Einstelldifferenzdruck zu finden, erfordert keine sort
und bietet zusätzliche Flexibilität.
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
Vielleicht zum Beispiel, würden Sie nur gerne den Unterschied in Linien finden, das nicht-negative Zahlen darstellen:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
Vielleicht haben Sie einen besseren Weg, brauchen es in Postgres zu tun, kann ich so ziemlich Wette, dass Sie nicht einen schnelleren Weg finden, es zu tun flache Dateien. Sie sollten eine einfache innere Verknüpfung und unter der Annahme, dass beide ID cols indiziert sind, dass sollte sehr schnell sein.
tun können Also, das ist etwas anders als die anderen Antworten. Ich kann nicht sagen, dass ein C ++ Compiler ist genau ein "Linux CLI-Tool", aber laufen g++ -O3 -march=native -o set_diff main.cpp
(mit dem Code unten in main.cpp
kann den Trick):
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
zu bedienen, einfach laufen set_diff B A
( nicht A B
, da B
ist nodes_to_keep
) und die daraus resultierende Differenz wird auf der Standardausgabe gedruckt werden.
Beachten Sie, dass ich habe entgangene ein paar C ++ Best Practices der Code einfacher zu halten.
Viele zusätzliche Geschwindigkeitsoptimierungen konnte (zu einem Preis von mehr Speicher) erfolgen. mmap
wäre auch besonders nützlich sein für große Datenmengen, aber das würde den Code viel mehr beteiligt machen.
Da Sie erwähnt, dass die Datensätze groß sind, dachte ich, dass zu einem Zeitpunkt nodes_to_delete
eine Linie zu lesen könnte eine gute Idee sein, den Speicherverbrauch zu reduzieren. Der Ansatz oben im Code ist nicht besonders effizient, wenn es viele Duplikate in Ihrem nodes_to_delete
ist. Auch Auftrag wird nicht beibehalten.
Etwas leichter zu kopieren und in bash
(d-Skipping Schaffung main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF