2010-12-06 7 views
96

Ich habe eine große Datei A (bestehend aus E-Mails), eine Zeile für jede Mail. Ich habe auch eine andere Datei B, die einen anderen Satz von Mails enthält.Wie entfernt man die Zeilen, die in Datei B von einer anderen Datei A erscheinen?

Welcher Befehl würde ich alle Adressen entfernen verwenden, die aus der Datei A. in der Datei B erscheinen

Also, wenn die Datei A enthalten:

A 
B 
C 

und Datei B enthalten:

B  
D 
E 

Dann Datei A sollte gelassen werden:

A 
C 

Jetzt weiß ich, das ist eine Frage, die möglicherweise häufiger gestellt wurde, aber ich fand nur one command online, die mir einen Fehler mit einem schlechten Trennzeichen gab.

Jede Hilfe wäre sehr willkommen! Jemand wird sicherlich einen cleveren One-Liner haben, aber ich bin nicht der Shell-Experte.

+0

möglich Duplikat von [Zeilen aus einer Datei löschen, die sich in einer anderen Datei befinden] (http://stackoverflow.com/questions/4780203/deleting-lines-from-one-file-which-are-in-an-other-file) – tripleee

+0

@tripleee Wohlgemerkt, meine ist ein wenig älter und der andere hat Stimmen, um als ein Betrüger dieser o geschlossen werden ne – slhck

+1

Die meisten, wenn die Antworten hier für sortierte Dateien sind, und die offensichtlichste fehlt, was natürlich nicht deine Schuld ist, aber das macht die andere allgemein nützlicher. – tripleee

Antwort

136
comm -23 file1 file2 

-23 unterdrückt die Zeilen, die in beiden Dateien vorhanden sind, oder nur in der Datei 2. Die Dateien müssen sortiert werden (sie in Ihrem Beispiel sind), aber wenn nicht, Rohr sie durch sort erste ...

Siehe man page here

+3

'comm -23 Datei1 Datei2> Datei3' wird Inhalt in Datei1 nicht in Datei2, in Datei3 ausgeben. Und dann würde 'mv file3 file1' endlich redundante Inhalte in file1 löschen. – 8090PZ

16

Ein anderer Weg, um die gleiche Sache zu tun (auch sortierte Eingabe erfordert):

join -v 1 fileA fileB 

In Bash, wenn die Dateien vorsortiert werden nicht:

join -v 1 <(sort fileA) <(sort fileB) 
4

Sie können dies tun, wenn Sie Ihre Dateien sortiert werden

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a 

--new-line-format ist für Linien, die in der Datei b sind aber nicht in einem --old-.. sind für Linien, die in der Datei ein, aber nicht in b --unchanged-.. ist für Linien, die in beiden sind. %L macht es so, dass die Zeile genau gedruckt wird.

man diff 

für mehr Details

+1

Sie sagen, dass dies funktioniert, wenn die Dateien nicht sortiert sind. Welche Probleme treten auf, wenn sie sortiert werden? Was ist, wenn sie teilweise sortiert sind? –

+1

Das war als Antwort auf die obige Lösung, die den Comm-Befehl vorgeschlagen hat. 'comm' erfordert, dass die Dateien sortiert werden. Wenn sie also sortiert sind, können Sie diese Lösung ebenfalls verwenden. Sie können diese Lösung verwenden, unabhängig davon, ob die Datei sortiert ist oder nicht, obwohl – aec

33

grep -Fvxf <lines-to-remove> <all-lines>

  • Arbeiten auf nicht-sortierten Dateien
  • behält die Reihenfolge
  • is POSIX
Beispiel

:

cat <<EOF > A 
b 
1 
a 
0 
01 
b 
1 
EOF 

cat <<EOF > B 
0 
1 
EOF 

grep -Fvxf B A 

Ausgang:

b 
a 
01 
b 

Erläuterung:

  • -F: Literalzeichenfolgen verwendet anstelle der Standard-BRE
  • -x: nur Spiele prüfen, die die gesamte Match Linie
  • -v: print
  • -f file nicht kongruente:

Diese Methode ist langsamer auf vorsortierten Dateien als andere Methoden Muster aus der angegebenen Datei übernehmen, da es allgemeiner ist. Wenn die Geschwindigkeit als auch zählt, finden Sie unter: Fast way of finding lines in one file that are not in another?

Siehe auch: https://unix.stackexchange.com/questions/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

25

awk zur Rettung!

Diese Lösung erfordert keine sortierten Eingaben. Sie müssen zuerst DateiB bereitstellen.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA 

kehrt

A 
C 

Wie es funktioniert?

NR==FNR{a[$0];next} Idiom ist die erste Datei in einem assoziativen Array als Schlüssel zum Speichern für eine spätere „enthält“ Test.

NR==FNR prüft, ob wir die erste Datei scannen, in der der globale Zeilenzähler (NR) dem aktuellen Dateizeilenzähler (FNR) entspricht.

a[$0] fügt die aktuelle Zeile auf die assoziativen Arrays als Schlüssel zu beachten, dass dies wie ein Satz verhält, wo es irgendwelche doppelte Werte (Schlüssel)

!($0 in a) wir in der nächsten Datei sind jetzt nicht (s), in ist ein enthält Test, hier wird überprüft, ob die aktuelle Zeile in der Menge ist, die wir im ersten Schritt aus der ersten Datei ausgefüllt haben, ! negiert die Bedingung. Was hier fehlt ist die Aktion, die standardmäßig {print} ist und normalerweise nicht explizit geschrieben wird.

Beachten Sie, dass dies jetzt verwendet werden kann, um schwarze Wörter zu entfernen.

$ awk '...' badwords allwords > goodwords 

mit einer leichten Änderung kann es mehrere Listen bereinigen und bereinigte Versionen erstellen.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ... 
+0

vollständige Markierungen auf diesem. Um dies auf der Kommandozeile in GnuWin32 in Windows zu verwenden, ersetzen Sie die einzelnen Nibbles durch doppelte Anführungszeichen. funktioniert ein Vergnügen. Danke vielmals. – twobob

+0

Das funktioniert aber wie kann ich die Ausgabe in DateiA in der Form von A umleiten (mit einer neuen Zeile) B –

+0

Ich denke du meinst A \ nC, schreibe zuerst in eine temporäre Datei und überschreibe die ursprüngliche Datei ' ...> tmp && mv tmp fileA' – karakfa

3

Diese Verfeinerung von @ Karakfas schöner Antwort kann bei sehr großen Dateien merklich schneller sein. Wie bei dieser Antwort muss keine Datei sortiert werden, aber die Geschwindigkeit wird durch die assoziativen Arrays von awk sichergestellt. Nur die Suchdatei wird im Speicher gehalten.

Diese Formulierung ermöglicht auch die Möglichkeit, dass nur ein bestimmtes Feld ($ N) in der Eingabedatei für den Vergleich verwendet wird.

# Print lines in the input unless the value in column $N 
# appears in a lookup file, $LOOKUP; 
# if $N is 0, then the entire line is used for comparison. 

awk -v N=$N -v lookup="$LOOKUP" ' 
    BEGIN { while (getline < lookup) { dictionary[$0]=$0 } } 
    !($N in dictionary) {print}' 

(Ein weiterer Vorteil dieses Ansatzes ist, dass es einfach ist, das Vergleichskriterium zu ändern, zB Vorder- und Hinterleerraum zu trimmen.)

+0

Dies ist schwieriger in einem Cross-Case Cross-Case Szenario als der andere Liner zu verwenden. Aber Hut für den Leistungsaufwand – twobob

0

Sie Python verwenden können:

python -c ' 
lines_to_remove = set() 
with open("file B", "r") as f: 
    for line in f.readlines(): 
     lines_to_remove.add(line.strip()) 

with open("file A", "r") as f: 
    for line in [line.strip() for line in f.readlines()]: 
     if line not in lines_to_remove: 
      print(line) 
' 
Verwandte Themen