Löschen von Zeilen aus einer Datei, die sich in einer anderen Datei befinden


126

Ich habe eine Datei f1:

line1
line2
line3
line4
..
..

Ich möchte alle Zeilen löschen, die sich in einer anderen Datei befinden f2:

line2
line8
..
..

Ich habe etwas mit catund ausprobiert sed, was nicht einmal dem entsprach, was ich beabsichtigt hatte. Wie kann ich das machen?



Wenn Sie Zeilen aus einer Datei entfernen möchten
rogerdpack

Antworten:


154

grep -v -x -f f2 f1 sollte den Trick machen.

Erläuterung:

  • -v nicht übereinstimmende Zeilen auswählen
  • -x nur ganze Zeilen abgleichen
  • -f f2 Muster von bekommen f2

Man kann stattdessen verwenden grep -Foder fgreppassen feste Strings von f2eher als Muster (im Fall , dass Sie die Zeilen in einer entfernen „ was Sie sehen , ob das, was man bekommt“ Art und Weise , anstatt die Linien bei der Behandlung f2als regex Muster).


22
Dies hat eine O (n²) -Komplexität und es wird Stunden dauern, bis die Dateien mehr als ein paar K Zeilen enthalten.
Arnaud Le Blanc

11
Das Herausfinden, welche SO-vorgeschlagenen Algorithmen eine O (n ^ 2) -Komplexität haben, hat nur eine O (n) -Komplexität, kann jedoch noch Stunden dauern, um zu konkurrieren.
HDave

2
Ich habe dies nur an 2 Dateien mit jeweils ~ 2k Zeilen versucht und es wurde vom Betriebssystem getötet (zugegeben, dies ist eine nicht so leistungsfähige VM, aber immer noch).
Trebor Rude

1
Ich liebe die Eleganz davon; Ich bevorzuge die Geschwindigkeit von Jona Christopher Sahnwals Antwort.
Alex Hall

1
@ arnaud576875: Bist du sicher? Es kommt auf die Umsetzung von an grep. Wenn es f2vor Beginn der Suche ordnungsgemäß vorverarbeitet wird, dauert die Suche nur O (n) Zeit.
HelloGoodbye

56

Versuchen Sie stattdessen comm (vorausgesetzt, f1 und f2 sind "bereits sortiert")

comm -2 -3 f1 f2

5
Ich bin nicht sicher, ob commdie Lösung die Frage hat, dass die Zeilen f1nicht sortiert sind, was eine Voraussetzung für die Verwendung istcomm
Gabuzo

1
Dies funktionierte für mich, da meine Dateien sortiert waren und mehr als 250.000 Zeilen in einer von ihnen hatten, nur 28.000 in der anderen. Vielen Dank!
Winter

1
Wenn dies funktioniert (Eingabedateien werden sortiert), ist dies extrem schnell!
Mike Jarvis

Wie in der Lösung von arnaud576875 wurden bei Verwendung von cygwin doppelte Zeilen in der zweiten Datei entfernt, die möglicherweise beibehalten werden sollen.
Alex Hall

8
Sie können Prozess-Substitution verwenden, um die Dateien zuerst zu sortieren, natürlich:comm -2 -3 <(sort f1) <(sort f2)
Davemyron

14

Um nicht zu große Dateien auszuschließen, können Sie die assoziativen Arrays von AWK verwenden.

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

Die Ausgabe erfolgt in derselben Reihenfolge wie die Datei "from-this.txt". Die tolower()Funktion macht es unabhängig von Groß- und Kleinschreibung, wenn Sie das brauchen.

Die algorithmische Komplexität wird wahrscheinlich O (n) (Größe von exclude-this.txt) + O (n) (Größe von-this.txt) sein.


Warum sagen Sie Dateien, die nicht zu groß sind? Die Angst hier ist (ich nehme an), dass awk das System nicht mehr über genügend Systemspeicher verfügt, um den Hash zu erstellen, oder gibt es eine andere Einschränkung?
Rogerdpack

Für Follower gibt es noch andere aggressivere Optionen, um die Zeilen zu "bereinigen" (da der Vergleich genau sein muss, um das assoziative Array zu verwenden), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: Eine große Ausschlussdatei erfordert ein großes Hash-Array (und eine lange Verarbeitungszeit). Eine große "from-this.txt" benötigt nur eine lange Verarbeitungszeit.
Bis auf weiteres angehalten.

1
Dies schlägt fehl (dh es wird keine Ausgabe erzeugt), wenn exclude-these.txtes leer ist. Die Antwort von @ jona-christopher-sahnwaldt unten funktioniert in diesem Fall. Sie können auch mehrere Dateien angeben, z. B.awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell

11

Ähnlich wie bei Dennis Williamsons Antwort (meistens syntaktische Änderungen, z. B. explizite Einstellung der Dateinummer anstelle des NR == FNRTricks):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

Durch den Zugriff r[$0]wird der Eintrag für diese Zeile erstellt, ohne dass ein Wert festgelegt werden muss.

Unter der Annahme, dass awk eine Hash-Tabelle mit konstanter Suche und (im Durchschnitt) konstanter Aktualisierungszeit verwendet, beträgt die zeitliche Komplexität O (n + m), wobei n und m die Länge der Dateien sind. In meinem Fall betrug n ~ 25 Millionen und m ~ 14000. Die awk-Lösung war viel schneller als sortieren, und ich zog es auch vor, die ursprüngliche Reihenfolge beizubehalten.


Wie unterscheidet sich das von der Antwort von Dennis Williamson? Ist der einzige Unterschied, dass es keine Zuweisung in den Hash macht, also etwas schneller als dieser? Die algorithmische Komplexität ist dieselbe wie seine?
Rogerdpack

Der Unterschied ist meist syntaktisch. Ich finde die Variable fklarer als NR == FNR, aber das ist Geschmackssache. Die Zuweisung zum Hash sollte so schnell erfolgen, dass zwischen den beiden Versionen kein messbarer Geschwindigkeitsunterschied besteht. Ich glaube, ich habe mich in Bezug auf die Komplexität geirrt. Wenn die Suche konstant ist, sollte auch die Aktualisierung (im Durchschnitt) konstant sein. Ich weiß nicht, warum ich dachte, das Update wäre logarithmisch. Ich werde meine Antwort bearbeiten.
jcsahnwaldt Reinstate Monica

Ich habe eine Reihe dieser Antworten ausprobiert, und diese war AMAZEBALLS schnell. Ich hatte Dateien mit Hunderttausenden von Zeilen. Lief wie am Schnürchen!
Herr T

1
Dies ist meine bevorzugte Lösung. Es funktioniert mit mehreren Dateien und auch leeren Ausschlussdateien, z awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out. Während die andere awkLösung mit einer leeren Ausschlussdatei fehlschlägt und nur eine nehmen kann.
Graham Russell

5

wenn Sie Ruby haben (1.9+)

#!/usr/bin/env ruby 
b=File.read("file2").split
open("file1").each do |x|
  x.chomp!
  puts x if !b.include?(x)
end

Welches hat O (N ^ 2) Komplexität. Wenn Sie sich für die Leistung interessieren, finden Sie hier eine andere Version

b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}

Dies verwendet einen Hash, um die Subtraktion zu bewirken, ebenso wie die Komplexität O (n) (Größe von a) + O (n) (Größe von b).

Hier ist ein kleiner Benchmark mit freundlicher Genehmigung von Benutzer 576875, jedoch mit 100.000 Zeilen, der oben genannten:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time ruby test.rb > ruby.test

real    0m0.639s
user    0m0.554s
sys     0m0.021s

$time sort file1 file2|uniq -u  > sort.test

real    0m2.311s
user    0m1.959s
sys     0m0.040s

$ diff <(sort -n ruby.test) <(sort -n sort.test)
$

diff wurde verwendet, um zu zeigen, dass es keine Unterschiede zwischen den 2 generierten Dateien gibt.


1
Dies hat eine O (n²) -Komplexität und es wird Stunden dauern, bis die Dateien mehr als ein paar K Zeilen enthalten.
Arnaud Le Blanc

Es ist mir an dieser Stelle nicht wirklich wichtig, weil er keine großen Dateien erwähnt hat.
Kurumi

3
Es ist nicht nötig, so defensiv zu sein, es ist nicht so, als ob @ user576875 Ihre Antwort oder irgendetwas herabgestimmt hätte. :-)
John Parker

sehr schöne zweite Version, Ruby gewinnt :)
Arnaud Le Blanc

4

Einige zeitliche Vergleiche zwischen verschiedenen anderen Antworten:

$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null

real    0m0.019s
user    0m0.023s
sys     0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null

real    0m0.026s
user    0m0.018s
sys     0m0.007s
$ time grep -xvf f2 f1 > /dev/null

real    0m43.197s
user    0m43.155s
sys     0m0.040s

sort f1 f2 | uniq -u ist nicht einmal ein symmetrischer Unterschied, da dadurch Zeilen entfernt werden, die in beiden Dateien mehrmals vorkommen.

comm kann auch mit stdin und hier Strings verwendet werden:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

Scheint ein Job zu sein, der für die SQLite-Shell geeignet ist:

create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify  .separator ××any_improbable_string×× 
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q

1

Hast du das mit sed versucht ?

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh

sed -i 's#$#%%g'"'"' f1#g' f2.sh

sed -i '1i#!/bin/bash' f2.sh

sh f2.sh

0

Keine 'Programmier'-Antwort, aber hier ist eine schnelle und schmutzige Lösung: Gehen Sie einfach zu http://www.listdiff.com/compare-2-lists-difference-tool .

Funktioniert natürlich nicht für große Dateien, aber es hat den Trick für mich getan. Ein paar Anmerkungen:

  • Ich bin in keiner Weise mit der Website verbunden (wenn Sie mir immer noch nicht glauben, können Sie einfach online nach einem anderen Tool suchen; ich habe den Suchbegriff "Differenzliste online setzen" verwendet).
  • Die verlinkte Website scheint bei jedem Listenvergleich Netzwerkanrufe zu tätigen. Geben Sie daher keine vertraulichen Daten ein
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.