Usuwanie wierszy z jednego pliku, które znajdują się w innym pliku


126

Mam plik f1:

line1
line2
line3
line4
..
..

Chcę usunąć wszystkie wiersze, które są w innym pliku f2:

line2
line8
..
..

Próbowałem czegoś z cati sed, co nie było nawet bliskie temu, co zamierzałem. W jaki sposób mogę to zrobić?



Jeśli chcesz usunąć linie z pliku, które „nawet zawierają” ciągi znaków z innego pliku (na przykład częściowe dopasowania), zobacz unix.stackexchange.com/questions/145079/ ...
rogerdpack

Odpowiedzi:


154

grep -v -x -f f2 f1 powinien załatwić sprawę.

Wyjaśnienie:

  • -v aby wybrać niepasujące wiersze
  • -x aby dopasować tylko całe linie
  • -f f2 aby uzyskać wzory z f2

Można zamiast tego użyć grep -Flub fgrepdopasować stałe ciągi ze f2zamiast wzorców (w przypadku, gdy chcesz usunąć wiersze w „co widzisz, jeśli to, co dostajesz” sposób zamiast leczenia linie f2jak regex wzorów).


22
Ma to złożoność O (n²) i będzie trwało kilka godzin, gdy pliki będą zawierały więcej niż kilka K wierszy.
Arnaud Le Blanc

11
Ustalenie, które algorytmy sugerowane przez SO mają złożoność O (n ^ 2), ma tylko złożoność O (n), ale nadal może trwać godzinami.
HDave

2
Właśnie wypróbowałem to na 2 plikach po ~ 2k wierszach każdy i został on zabity przez system operacyjny (z pewnością nie jest to tak potężna maszyna wirtualna, ale nadal).
Trebor Rude

1
Uwielbiam elegancję tego; Wolę szybkość odpowiedzi Jony Christophera Sahnwal.
Alex Hall

1
@ arnaud576875: Czy na pewno? To zależy od implementacji grep. Jeśli wstępnie przetwarza f2prawidłowo, zanim rozpocznie wyszukiwanie, wyszukiwanie zajmie tylko O ​​(n) czasu.
HelloGoodbye

57

Zamiast tego spróbuj comm (zakładając, że f1 i f2 są już „posortowane”)

comm -2 -3 f1 f2

5
Nie jestem pewien, czy commrozwiązanie ma pytanie, które nie wskazuje, że linie f1są posortowane, co jest warunkiem wstępnym użyciacomm
gabuzo

1
To zadziałało dla mnie, ponieważ moje pliki zostały posortowane i miały ponad 250 000 wierszy w jednym z nich, a tylko 28 000 w drugim. Dzięki!
Zima

1
Kiedy to działa (pliki wejściowe są sortowane), jest to niezwykle szybkie!
Mike Jarvis

Podobnie jak w rozwiązaniu arnaud576875, dla mnie używając cygwin, wyeliminowało to zduplikowane linie w drugim pliku, które mogą chcieć zostać zachowane.
Alex Hall

9
Możesz oczywiście użyć podstawiania procesów, aby najpierw posortować pliki:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

W przypadku plików wykluczeń, które nie są zbyt duże, można użyć tablic asocjacyjnych AWK.

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

Dane wyjściowe będą w tej samej kolejności, co plik „from-this.txt”. tolower()Funkcja sprawia, że wielkość liter, jeśli potrzeba.

Algorytmiczna złożoność prawdopodobnie będzie wynosić O (n) (rozmiar exclude-these.txt) + O (n) (rozmiar from-this.txt)


Dlaczego mówisz o plikach, które nie są zbyt duże? Obawa polega na (zakładam), że awk uruchamia system z pamięci systemowej w celu utworzenia skrótu, czy też jest jakieś inne ograniczenie?
rogerdpack

dla obserwujących istnieje jeszcze inna, bardziej agresywna opcja "oczyszczenia" linii (ponieważ porównanie musi być dokładne, aby użyć tablicy asocjacyjnej), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: duży plik wykluczeń będzie wymagał dużej tablicy skrótów (i długiego czasu przetwarzania). Duży plik „from-this.txt” będzie wymagał tylko długiego czasu przetwarzania.
Wstrzymano do odwołania.

1
To się nie powiedzie (tj. Nie daje żadnych danych wyjściowych), jeśli exclude-these.txtjest puste. Poniższa odpowiedź @ jona-christopher-sahnwaldt działa w tym przypadku. Możesz także określić wiele plików, np.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

Podobnie jak odpowiedź Dennisa Williamsona (głównie zmiany składniowe, np. Jawne ustawienie numeru pliku zamiast NR == FNRsztuczki):

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

Dostęp r[$0]tworzy wpis dla tej linii, nie ma potrzeby ustawiania wartości.

Zakładając, że awk używa tablicy skrótów ze stałym wyszukiwaniem i (średnio) stałym czasem aktualizacji, złożoność czasowa będzie wynosić O (n + m), gdzie n i m to długości plików. W moim przypadku n było ~ 25 milionów im ~ 14000. Rozwiązanie awk było znacznie szybsze niż sortowanie, wolałem też zachować pierwotną kolejność.


Czym to się różni od odpowiedzi Dennisa Williamsona? Czy jedyną różnicą jest to, że nie wykonuje przypisania do skrótu, więc jest nieco szybszy? Złożoność algorytmiczna jest taka sama jak jego?
rogerdpack

Różnica jest głównie syntaktyczna. Uważam, że zmienna jest fwyraźniejsza niż NR == FNR, ale to kwestia gustu. Przypisanie do skrótu powinno być tak szybkie, że nie ma mierzalnej różnicy prędkości między dwiema wersjami. Myślę, że myliłem się co do złożoności - jeśli wyszukiwanie jest stałe, aktualizacja również powinna być stała (średnio). Nie wiem, dlaczego myślałem, że aktualizacja będzie logarytmiczna. Zmienię odpowiedź.
jcsahnwaldt Przywróć Monikę

Wypróbowałem kilka takich odpowiedzi, a ta była szybka w AMAZEBALLS. Miałem pliki z setkami tysięcy wierszy. Działał jak urok!
Mr. T

1
To jest moje preferowane rozwiązanie. Działa z wieloma plikami, a także pustymi plikami wykluczeń, np 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. Podczas gdy inne awkrozwiązanie zawodzi z pustym plikiem wykluczeń i może zająć tylko jeden.
Graham Russell

5

jeśli masz Ruby (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

Który ma złożoność O (N ^ 2). Jeśli chcesz zadbać o wydajność, oto inna wersja

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

który używa skrótu do odejmowania, więc jest złożoność O (n) (rozmiar a) + O (n) (rozmiar b)

oto mały test porównawczy, dzięki uprzejmości użytkownika576875, ale z 100 tys. linii, z powyższego:

$ 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 został użyty do pokazania, że ​​nie ma różnic między dwoma wygenerowanymi plikami.


1
Ma to złożoność O (n²) i będzie trwało kilka godzin, gdy pliki będą zawierały więcej niż kilka K wierszy.
Arnaud Le Blanc

nie bardzo mnie to obchodzi, ponieważ nie wspomniał o żadnych dużych plikach.
kurumi,

3
Nie ma potrzeby być tak defensywnym, to nie jest tak, że @ user576875 zaniżał twoją odpowiedź lub cokolwiek. :-)
John Parker

bardzo ładna druga wersja, wygrywa rubin :)
Arnaud Le Blanc

4

Kilka porównań czasowych między różnymi innymi odpowiedziami:

$ 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 nie jest nawet symetryczną różnicą, ponieważ usuwa linie, które pojawiają się wiele razy w obu plikach.

comm może być również używany ze stdin i tutaj ciągami:

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

2

Wydaje się, że jest to praca odpowiednia dla powłoki SQLite:

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

Czy próbowałeś tego z sedem?

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

Nie jest to odpowiedź na „programowanie”, ale tutaj jest szybkie i brudne rozwiązanie: po prostu przejdź do http://www.listdiff.com/compare-2-lists-difference-tool .

Oczywiście nie będzie działać w przypadku dużych plików, ale mi się to udało. Kilka uwag:

  • Nie jestem w żaden sposób powiązany z witryną (jeśli nadal mi nie wierzysz, możesz po prostu poszukać innego narzędzia online; użyłem hasła „ustaw listę różnic online”)
  • Wydaje się, że połączona witryna wykonuje połączenia sieciowe przy każdym porównaniu list, więc nie podawaj jej żadnych poufnych danych
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.