Eu tenho um arquivo f1
:
line1
line2
line3
line4
..
..
Eu quero excluir todas as linhas que estão em outro arquivo f2
:
line2
line8
..
..
Eu tentei algo com cat
e sed
, que não era nem perto do que eu pretendia. Como posso fazer isso?
Eu tenho um arquivo f1
:
line1
line2
line3
line4
..
..
Eu quero excluir todas as linhas que estão em outro arquivo f2
:
line2
line8
..
..
Eu tentei algo com cat
e sed
, que não era nem perto do que eu pretendia. Como posso fazer isso?
Respostas:
grep -v -x -f f2 f1
deve fazer o truque.
Explicação:
-v
para selecionar linhas não correspondentes-x
para corresponder apenas a linhas inteiras-f f2
para obter padrões de f2
Em vez disso, pode-se usar grep -F
ou fgrep
combinar seqüências de caracteres fixas em f2
vez de padrões (no caso de você desejar remover as linhas da maneira "o que você vê se obtém", em vez de tratar as linhas f2
como padrões regex).
grep
. Se ele se processar f2
adequadamente antes de iniciar a pesquisa, a pesquisa levará apenas O (n) tempo.
Tente comm em vez disso (assumindo que f1 e f2 "já estão classificados")
comm -2 -3 f1 f2
comm
é a solução tem a questão não indica que as linhas f1
são classificadas que é um pré-requisito para o usocomm
comm -2 -3 <(sort f1) <(sort f2)
Para excluir arquivos que não são muito grandes, você pode usar as matrizes associativas do AWK.
awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt
A saída será na mesma ordem que o arquivo "from-this.txt". A tolower()
função faz distinção entre maiúsculas e minúsculas, se você precisar.
A complexidade algorítmica provavelmente será O (n) (tamanho excluir-estes.txt) + O (n) (tamanho-deste.txt)
exclude-these.txt
estiver vazio. A resposta de @ jona-christopher-sahnwaldt abaixo funciona nesse caso. Você também pode especificar arquivos múltiplos por exemploawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Semelhante à resposta de Dennis Williamson (principalmente alterações sintáticas, por exemplo, definir explicitamente o número do arquivo em vez do NR == FNR
truque):
awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt
O acesso r[$0]
cria a entrada para essa linha, sem a necessidade de definir um valor.
Supondo que o awk use uma tabela de hash com pesquisa constante e (em média) tempo de atualização constante, a complexidade do tempo será O (n + m), onde n e m são os comprimentos dos arquivos. No meu caso, n era ~ 25 milhões e m ~ 14000. A solução awk era muito mais rápida que a classificação, e eu também preferia manter a ordem original.
f
mais clara que NR == FNR
, mas isso é uma questão de gosto. A atribuição no hash deve ser tão rápida que não há diferença de velocidade mensurável entre as duas versões. Eu acho que estava errado sobre a complexidade - se a pesquisa é constante, a atualização também deve ser constante (em média). Não sei por que pensei que a atualização seria logarítmica. Vou editar minha resposta.
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
. Considerando que a outra awk
solução falha com o arquivo de exclusão vazio e pode levar apenas um.
se você tem 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
O qual possui complexidade O (N ^ 2). Se você quer se preocupar com desempenho, aqui está outra versão
b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}
que usa um hash para efetuar a subtração, assim como a complexidade O (n) (tamanho de a) + O (n) (tamanho de b)
aqui está uma pequena referência, cortesia do user576875, mas com 100 mil linhas, acima:
$ 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
foi usado para mostrar que não há diferenças entre os 2 arquivos gerados.
Algumas comparações de tempo entre várias outras respostas:
$ 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
nem sequer é uma diferença simétrica, porque remove linhas que aparecem várias vezes em qualquer arquivo.
comm também pode ser usado com stdin e aqui strings:
echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a
Parece ser um trabalho adequado para o shell 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
Não é uma resposta de 'programação', mas aqui está uma solução rápida e suja: basta ir para http://www.listdiff.com/compare-2-lists-difference-tool .
Obviamente não funcionará para arquivos enormes, mas fez o truque para mim. Algumas notas: