Se você deseja remover linhas de um arquivo que "contém até" cadeias de outro arquivo (por exemplo, correspondências parciais), consulte unix.stackexchange.com/questions/145079/…
rogerdpack
Respostas:
154
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 -Fou fgrepcombinar seqüências de caracteres fixas em f2vez 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 f2como padrões regex).
Isso tem complexidade O (n²) e começará a demorar horas para ser concluído, uma vez que os arquivos contenham mais do que algumas linhas K.
Arnaud Le Blanc
11
Descobrir quais algoritmos sugeridos pelo SO têm complexidade O (n ^ 2) têm complexidade O (n), mas ainda podem levar horas para competir.
HDave
2
Eu apenas tentei isso em 2 arquivos de ~ 2k linhas cada, e foi morto pelo sistema operacional (concedido, essa é uma VM não tão poderosa, mas ainda assim).
Trebor rude
1
Eu amo a elegância disso; Prefiro a velocidade da resposta de Jona Christopher Sahnwal.
Alex Hall
1
@ arnaud576875: Você tem certeza? Depende da implementação de grep. Se ele se processar f2adequadamente antes de iniciar a pesquisa, a pesquisa levará apenas O (n) tempo.
HelloGoodbye
56
Tente comm em vez disso (assumindo que f1 e f2 "já estão classificados")
Por que você diz arquivos que não são muito grandes? O medo aqui é (presumo) awk executando o sistema a partir da memória do sistema para criar o hash, ou há alguma outra limitação?
Rogerdpack #
para os seguidores, há ainda outra opção mais agressiva para "sanear" as linhas (uma vez que a comparação tem que ser exato para usar a matriz associativa), ex unix.stackexchange.com/a/145132/8337
rogerdpack
@rogerdpack: um arquivo de exclusão grande exigirá uma grande variedade de hash (e um longo tempo de processamento). Um "from-this.txt" grande exigirá apenas um longo tempo de processamento.
Pausado até novo aviso.
1
Isso falha (ou seja, não produz nenhuma saída) se exclude-these.txtestiver 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
Graham Russell
11
Semelhante à resposta de Dennis Williamson (principalmente alterações sintáticas, por exemplo, definir explicitamente o número do arquivo em vez do NR == FNRtruque):
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.
Como isso difere da resposta de Dennis Williamson? A única diferença é que ele não faz uma atribuição no hash, tão ligeiramente mais rápido que isso? Complexidade algorítmica é a mesma que a dele?
Rogerdpack #
A diferença é principalmente sintática. Acho a variável fmais 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.
Jcsahnwaldt Reinstate Monica
Eu tentei várias dessas respostas, e essa foi rápida no AMAZEBALLS. Eu tinha arquivos com centenas de milhares de linhas. Trabalhou como um encanto!
Mr. T
1
Esta é a minha solução preferida. Ele funciona com vários arquivos e também arquivos de exclusão vazios, por exemplo 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 awksolução falha com o arquivo de exclusão vazio e pode levar apenas um.
Graham Russell
5
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
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 1100000);do echo "$i";done|sort --random-sort > file1
$ for i in $(seq 12100000);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.
Isso tem complexidade O (n²) e começará a demorar horas para ser concluído, uma vez que os arquivos contenham mais do que algumas linhas K.
Arnaud Le Blanc
Eu realmente não me importo neste momento, porque ele não mencionou nenhum arquivo grande.
kurumi
3
Não há necessidade de ser tão defensivo, não é como se @ user576875 diminuísse sua resposta ou algo assim. :-)
John Parker
muito bom segunda versão, vitórias rubi :)
Arnaud Le Blanc
4
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
Obviamente não funcionará para arquivos enormes, mas fez o truque para mim. Algumas notas:
Não sou afiliado ao site de forma alguma (se você ainda não acredita em mim, basta procurar uma ferramenta on-line diferente; usei o termo de pesquisa "definir lista de diferenças on-line")
O site vinculado parece fazer chamadas de rede em todas as comparações de lista, portanto, não alimente dados confidenciais
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 def2
Em vez disso, pode-se usar
grep -F
oufgrep
combinar seqüências de caracteres fixas emf2
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 linhasf2
como padrões regex).fonte
grep
. Se ele se processarf2
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")
fonte
comm
é a solução tem a questão não indica que as linhasf1
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.
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)
fonte
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.
fonte
f
mais clara queNR == 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 outraawk
solução falha com o arquivo de exclusão vazio e pode levar apenas um.se você tem Ruby (1.9+)
O qual possui complexidade O (N ^ 2). Se você quer se preocupar com desempenho, aqui está outra versão
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:
diff
foi usado para mostrar que não há diferenças entre os 2 arquivos gerados.fonte
Algumas comparações de tempo entre várias outras respostas:
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:
fonte
Parece ser um trabalho adequado para o shell SQLite:
fonte
Você tentou isso com sed?
fonte
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:
fonte