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 cat
i sed
, co nie było nawet bliskie temu, co zamierzałem. W jaki sposób mogę to zrobić?
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 cat
i sed
, co nie było nawet bliskie temu, co zamierzałem. W jaki sposób mogę to zrobić?
Odpowiedzi:
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 -F
lub fgrep
dopasować stałe ciągi ze f2
zamiast wzorców (w przypadku, gdy chcesz usunąć wiersze w „co widzisz, jeśli to, co dostajesz” sposób zamiast leczenia linie f2
jak regex wzorów).
grep
. Jeśli wstępnie przetwarza f2
prawidłowo, zanim rozpocznie wyszukiwanie, wyszukiwanie zajmie tylko O (n) czasu.
Zamiast tego spróbuj comm (zakładając, że f1 i f2 są już „posortowane”)
comm -2 -3 f1 f2
comm
rozwiązanie ma pytanie, które nie wskazuje, że linie f1
są posortowane, co jest warunkiem wstępnym użyciacomm
comm -2 -3 <(sort f1) <(sort f2)
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)
exclude-these.txt
jest 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
Podobnie jak odpowiedź Dennisa Williamsona (głównie zmiany składniowe, np. Jawne ustawienie numeru pliku zamiast NR == FNR
sztuczki):
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ść.
f
wyraź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ź.
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 awk
rozwiązanie zawodzi z pustym plikiem wykluczeń i może zająć tylko jeden.
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.
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
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
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: