Excluindo linhas de um arquivo que estão em outro arquivo

Eu tenho um arquivo f1 :

 line1 line2 line3 line4 .. .. 

Eu quero apagar 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?

grep -v -x -f f2 f1 deve fazer o truque.

Explicação:

  • -v para selecionar linhas não correspondentes
  • -x para combinar apenas linhas inteiras
  • -f f2 para obter padrões de f2

Em vez disso, pode-se usar -F f2 para combinar strings fixas de f2 ao invés de padrões (caso você queira remover as linhas de uma maneira “o que você vê se você obtiver” em vez de tratar as linhas em f2 como padrões regex).

Tente comm em vez disso (assumindo que f1 e f2 já estão “classificados”)

 comm -2 -3 f1 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 function tolower() torna insensível a maiúsculas e minúsculas, se você precisar disso.

A complexidade algorítmica provavelmente será O (n) (tamanho exclude-these.txt) + O (n) (tamanho from-this.txt)

Semelhante à resposta de Dennis Williamson (principalmente alterações sintáticas, por exemplo, definindo o número do arquivo explicitamente em vez do truque NR == FNR ):

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

Acessar r[$0] cria a input para essa linha, não é necessário 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 de tempo será O (n + m), onde n e m são os comprimentos dos arquivos. No meu caso, n foi ~ 25 milhões e m ~ 14000. A solução do awk era muito mais rápida do que o tipo, e eu também preferia manter a ordem original.

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 

Que tem 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 (ab).each {|x| puts x} 

que usa um hash para efetuar a subtração, assim é a complexidade O (n) (tamanho de a) + O (n) (tamanho de b)

aqui está um pequeno benchmark, cortesia de user576875, mas com 100 mil linhas, das opções 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 sort f1 f2 | uniq -u não é 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 

Você tentou isso com sed?

 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