Wie entferne ich die Zeilen, die in Datei B erscheinen, aus einer anderen Datei A?

164

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.

Mit welchem ​​Befehl würde ich alle in Datei B angezeigten Adressen aus Datei A entfernen.

Wenn also Datei A enthält:

A
B
C

und Datei B enthalten:

B    
D
E

Dann sollte Datei A belassen werden mit:

A
C

Jetzt weiß ich, dass dies eine Frage ist, die möglicherweise häufiger gestellt wurde, aber ich habe nur einen Befehl online gefunden , der mir einen Fehler mit einem schlechten Trennzeichen gegeben hat.

Jede Hilfe wäre sehr dankbar! Jemand wird sich sicherlich einen cleveren Einzeiler einfallen lassen, aber ich bin kein Shell-Experte.

slhck
quelle
1
Die meisten, wenn die Antworten hier für sortierte Dateien sind und die offensichtlichste fehlt, was natürlich nicht Ihre Schuld ist, aber das macht die andere allgemeiner nützlich.
Tripleee

Antworten:

208

Wenn die Dateien sortiert sind (in Ihrem Beispiel):

comm -23 file1 file2

-23Unterdrückt die Zeilen in beiden Dateien oder nur in Datei 2. Wenn die Dateien nicht sortiert sind, leiten Sie sie sortzuerst durch ...

Siehe die Manpage hier

Der archetypische Paulus
quelle
9
comm -23 file1 file2 > file3gibt Inhalte in Datei1 aus, nicht in Datei2, in Datei3. Und mv file3 file1würde dann endlich redundante Inhalte in Datei1 löschen.
Spektral
2
Alternativ verwenden comm -23 file1 file2 | sponge file1. Keine Bereinigung erforderlich.
Socowi
Manpage Link wird für mich nicht geladen
Felix Rabe
@Socowi Was ist Schwamm? Ich habe das nicht auf meinem System. (Macos 10.13)
Felix Rabe
@ FelixRabe, na ja, das ist lästig. Ersetzt durch Ihren Link. Danke
Der archetypische Paul
86

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

  • funktioniert mit nicht sortierten Dateien
  • pflegt die Reihenfolge
  • ist POSIX

Beispiel:

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

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Ausgabe:

b
a
01
b

Erläuterung:

  • -F: Verwenden Sie Literalzeichenfolgen anstelle der Standard-BRE
  • -x: Berücksichtigen Sie nur Übereinstimmungen, die mit der gesamten Zeile übereinstimmen
  • -v: Druck nicht übereinstimmend
  • -f file: Nehmen Sie Muster aus der angegebenen Datei

Diese Methode ist bei vorsortierten Dateien langsamer als bei anderen Methoden, da sie allgemeiner ist. Wenn auch Geschwindigkeit wichtig ist, siehe: Schnelle Suche nach Zeilen in einer Datei, die sich nicht in einer anderen befinden?

Hier ist eine schnelle Bash-Automatisierung für den Inline-Betrieb:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub stromaufwärts .

Verwendung:

remove-lines lines-to-remove remove-from-this-file

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

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
57

awk zur Rettung!

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

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

kehrt zurück

A
C

Wie funktioniert es?

NR==FNR{a[$0];next} idiom dient zum Speichern der ersten Datei in einem assoziativen Array als Schlüssel für einen späteren "enthält" -Test.

NR==FNR prüft, ob die erste Datei gescannt wird, wobei der globale Zeilenzähler (NR) dem aktuellen Dateizeilenzähler (FNR) entspricht.

a[$0] Fügt die aktuelle Zeile dem assoziativen Array als Schlüssel hinzu. Beachten Sie, dass sich dies wie eine Menge verhält, bei der keine doppelten Werte (Schlüssel) vorhanden sind.

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

Beachten Sie, dass dies jetzt zum Entfernen von Wörtern auf der schwarzen Liste verwendet werden kann.

$ awk '...' badwords allwords > goodwords

Mit einer geringfügigen Änderung können mehrere Listen bereinigt und bereinigte Versionen erstellt werden.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
Karakfa
quelle
volle Punktzahl dazu. Um dies in der Befehlszeile in GnuWin32 unter Windows zu verwenden, ersetzen Sie die einfachen Halbbytes durch doppelte Anführungszeichen. arbeitet ein Vergnügen. Danke vielmals.
Twobob
Dies funktioniert, aber wie kann ich die Ausgabe in Datei A in Form von A (mit einer neuen Zeile) B
Anand Builders
Ich denke du meinst A\nC, schreibe zuerst in eine temporäre Datei und überschreibe die Originaldatei... > tmp && mv tmp fileA
Karakfa
Volle Punktzahl auch hier von mir. Diese awk benötigt 1 Sekunde, um eine Datei mit 104.000 Einträgen zu verarbeiten: +1:
MitchellK
Wenn Sie dies in Skripten verwenden, stellen Sie zunächst sicher, dass fileBnicht leer ist (0 Byte lang). Andernfalls wird anstelle des erwarteten Inhalts von ein leeres Ergebnis angezeigt fileA. (Ursache: FNR==NRgilt fileAdann.)
Peter Nowee
18

Eine andere Möglichkeit, dasselbe zu tun (erfordert auch sortierte Eingaben):

join -v 1 fileA fileB

Wenn die Dateien in Bash nicht vorsortiert sind:

join -v 1 <(sort fileA) <(sort fileB)
Bis auf weiteres angehalten.
quelle
7

Sie können dies tun, es sei denn, Ihre Dateien sind sortiert

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

--new-line-formatist für Zeilen, die in Datei b, aber nicht in a sind, --old-..ist für Zeilen, die in Datei a, aber nicht in b sind, --unchanged-..ist für Zeilen, die in beiden sind. %Lmacht es so, dass die Linie genau gedruckt wird.

man diff

für mehr Details

aec
quelle
1
Sie sagen, dies funktioniert nur, wenn die Dateien sortiert sind. Welche Probleme treten auf, wenn sie sortiert werden? Was ist, wenn sie teilweise sortiert sind?
Carlos Macasaet
1
Dies war eine Reaktion auf die obige Lösung, die die Verwendung von commBefehlen vorschlug . commerfordert, dass die Dateien sortiert werden. Wenn sie also sortiert sind, können Sie diese Lösung auch verwenden. Sie können diese Lösung verwenden, unabhängig davon, ob die Datei sortiert ist oder nicht
aec
7

Diese Verfeinerung der netten Antwort von @ karakfa kann bei sehr großen Dateien spürbar 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 gespeichert.

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

# 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 besteht darin, dass das Vergleichskriterium leicht geändert werden kann, z. B. um führende und nachfolgende Leerzeichen zu kürzen.)

Gipfel
quelle
Dies ist in einem plattformübergreifenden Szenario mit Eckfall schwieriger zu verwenden als bei dem anderen Liner. Allerdings hats für die Leistung Anstrengung
twobob
2

Sie können Python verwenden:

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)
'
Hallo Auf Wiedersehen
quelle
2

Sie können verwenden - diff fileA fileB | grep "^>" | cut -c3- > fileA

Dies funktioniert für Dateien, die nicht ebenfalls sortiert sind.

Darpan
quelle
-1

Um gemeinsame Zeilen zwischen zwei Dateien zu entfernen, können Sie den Befehl grep, comm oder join verwenden.

grep funktioniert nur für kleine Dateien. Verwenden Sie -v zusammen mit -f.

grep -vf file2 file1 

Dies zeigt Zeilen aus Datei1 an, die mit keiner Zeile in Datei2 übereinstimmen.

comm ist ein Dienstprogrammbefehl, der mit lexikalisch sortierten Dateien arbeitet. Es nimmt zwei Dateien als Eingabe und erzeugt drei Textspalten als Ausgabe: Zeilen nur in der ersten Datei; Zeilen nur in der zweiten Datei; und Zeilen in beiden Dateien. Sie können das Drucken einer beliebigen Spalte unterdrücken, indem Sie die Optionen -1, -2 oder -3 entsprechend verwenden.

comm -1 -3 file2 file1

Dies zeigt Zeilen aus Datei1 an, die mit keiner Zeile in Datei2 übereinstimmen.

Schließlich gibt es join, einen Dienstprogrammbefehl, der eine Gleichheitsverknüpfung für die angegebenen Dateien ausführt. Mit der Option -v können auch gemeinsame Zeilen zwischen zwei Dateien entfernt werden.

join -v1 -v2 file1 file2
Aakarsh Gupta
quelle
All dies wurde bereits in anderen Antworten gegeben. Ihr grep man braucht ein -F, oder Sie werden seltsame Ergebnisse erhalten, wenn die Linien wie reguläre Ausdrücke aussehen
The Archetypal Paul