Como remover as linhas que aparecem no arquivo B de outro arquivo A?

160

Eu tenho um arquivo grande A (composto por emails), uma linha para cada email. Eu também tenho outro arquivo B que contém outro conjunto de mensagens.

Qual comando eu usaria para remover todos os endereços que aparecem no arquivo B do arquivo A.

Portanto, se o arquivo A contiver:

A
B
C

e o arquivo B continha:

B    
D
E

Em seguida, o arquivo A deve ser deixado com:

A
C

Agora sei que essa é uma pergunta que pode ter sido feita com mais frequência, mas só encontrei um comando online que me deu um erro com um delimitador ruim.

Qualquer ajuda seria muito apreciada! Alguém certamente apresentará uma frase inteligente, mas eu não sou o especialista em shell.

slhck
fonte
1
A maioria se as respostas aqui são para arquivos classificados e a mais óbvia está faltando, o que obviamente não é sua culpa, mas isso torna a outra mais geralmente útil.
Tripleee 5/10

Respostas:

202

Se os arquivos estiverem classificados (eles estão no seu exemplo):

comm -23 file1 file2

-23suprime as linhas que estão nos dois arquivos ou apenas no arquivo 2. Se os arquivos não forem classificados, passe-os sortprimeiro ...

Veja a página de manual aqui

O Arquetípico Paulo
fonte
8
comm -23 file1 file2 > file3produzirá o conteúdo no arquivo1 e não no arquivo2, para o arquivo3. E, mv file3 file1finalmente, limparia o conteúdo redundante no arquivo1.
Spectral
2
Como alternativa, use comm -23 file1 file2 | sponge file1. Nenhuma limpeza necessária.
Socowi 13/0318
Link da página homem não está carregando para mim - alternativa: linux.die.net/man/1/comm
Felix Rabe
@Socowi O que é esponja? Eu não tenho isso no meu sistema. (macos 10.13)
Felix Rabe
@ FelixRabe, bem, isso é cansativo. Substituído pelo seu link. Obrigado
The Archetypal Paul
84

grep -Fvxf <lines-to-remove> <all-lines>

  • funciona em arquivos não classificados
  • mantém a ordem
  • é POSIX

Exemplo:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Resultado:

b
a
01
b

Explicação:

  • -F: use cadeias literais em vez do BRE padrão
  • -x: considere apenas correspondências que correspondem à linha inteira
  • -v: imprimir sem correspondência
  • -f file: pegue padrões do arquivo fornecido

Esse método é mais lento em arquivos pré-classificados que outros métodos, pois é mais geral. Se a velocidade também for importante, consulte: Maneira rápida de encontrar linhas em um arquivo que não está em outro?

Aqui está uma automação rápida do bash para operação em linha:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub upstream .

uso:

remove-lines lines-to-remove remove-from-this-file

Consulte também: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

Ciro Santilli adicionou uma nova foto
fonte
55

awk para o resgate!

Esta solução não requer entradas classificadas. Você precisa fornecer o arquivo B primeiro.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

retorna

A
C

Como funciona?

NR==FNR{a[$0];next} O idioma é para armazenar o primeiro arquivo em uma matriz associativa como chaves para um teste posterior "contém".

NR==FNR está verificando se estamos verificando o primeiro arquivo, em que o contador de linha global (NR) é igual ao contador de linha de arquivo atual (FNR).

a[$0] adiciona a linha atual à matriz associativa como chave, observe que isso se comporta como um conjunto, onde não haverá valores duplicados (chaves)

!($0 in a)agora estamos no (s) próximo (s) arquivo (s), iné um teste de contém, aqui está verificando se a linha atual está no conjunto que preenchemos na primeira etapa do primeiro arquivo, !nega a condição. O que está faltando aqui é a ação, que por padrão é {print}e geralmente não é escrita explicitamente.

Observe que agora isso pode ser usado para remover palavras da lista negra.

$ awk '...' badwords allwords > goodwords

com uma pequena alteração, ele pode limpar várias listas e criar versões limpas.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
karakfa
fonte
marcas completas sobre isso. Para usar isso na linha de comando do GnuWin32 no Windows, substitua os petiscos simples por aspas duplas. funciona um deleite. Muito Obrigado.
Twobob
Isso funciona, mas como poderei redirecionar a saída para fileA na forma de A (com uma nova linha) B
Anand Builders
Eu acho que você quer dizer A\nC, a escrita para um arquivo temporário primeiro e substituir o arquivo original... > tmp && mv tmp fileA
karakfa
Marcas cheias nisso também de mim. Este awk leva 1 segundo para processar um arquivo com 104.000 entradas: +1:
MitchellK
Ao usar isso em scripts, verifique primeiro fileBse não está vazio (0 bytes de comprimento), pois, se estiver, você obterá um resultado vazio em vez do conteúdo esperado de fileA. (Causa: FNR==NRserá aplicada a fileAentão.)
Peter Nowee
18

Outra maneira de fazer a mesma coisa (também requer entrada classificada):

join -v 1 fileA fileB

No Bash, se os arquivos não forem pré-classificados:

join -v 1 <(sort fileA) <(sort fileB)
Pausado até novo aviso.
fonte
7

Você pode fazer isso, a menos que seus arquivos sejam classificados

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formaté para linhas que estão no arquivo b, mas não em a, --old-..é para linhas que estão no arquivo a, mas não em b, --unchanged-..é para linhas que estão em ambos. %Lfaz com que a linha seja impressa exatamente.

man diff

para mais detalhes

aec
fonte
1
Você diz que isso funcionará, a menos que os arquivos sejam classificados. Quais problemas ocorrem se eles são classificados? E se eles forem parcialmente classificados?
Carlos Macasaet 24/09/2015
1
Isso foi em resposta à solução acima que sugeriu o uso do commcomando. commrequer que os arquivos sejam classificados, portanto, se eles forem classificados, você poderá usar essa solução também. Você pode usar esta solução, independentemente do arquivo é ordenado ou não embora
aec
7

Esse refinamento da boa resposta do @ karakfa pode ser notavelmente mais rápido para arquivos muito grandes. Como com essa resposta, nenhum dos arquivos precisa ser classificado, mas a velocidade é garantida em virtude das matrizes associativas do awk. Somente o arquivo de pesquisa é mantido na memória.

Essa formulação também permite a possibilidade de que apenas um campo específico ($ N) no arquivo de entrada seja usado na comparação.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Outra vantagem dessa abordagem é que é fácil modificar o critério de comparação, por exemplo, para aparar os espaços em branco à esquerda e à direita.)

pico
fonte
Isso é mais difícil de usar em um cenário de plataforma cruzada de esquina do que o outro liner. No entanto,
tiramos o
2

Você pode usar o Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'
Olá adeus
fonte
2

Você pode usar - diff fileA fileB | grep "^>" | cut -c3- > fileA

Isso funcionará para arquivos que não são classificados também.

Darpan
fonte
-1

Para remover linhas comuns entre dois arquivos, você pode usar o comando grep, comm ou join.

O grep funciona apenas para arquivos pequenos. Use -v junto com -f.

grep -vf file2 file1 

Isso exibe linhas do arquivo1 que não correspondem a nenhuma linha do arquivo2.

comm é um comando utilitário que funciona em arquivos classificados lexicamente. Leva dois arquivos como entrada e produz três colunas de texto como saída: linhas apenas no primeiro arquivo; linhas apenas no segundo arquivo; e linhas nos dois arquivos. Você pode suprimir a impressão de qualquer coluna usando a opção -1, -2 ou -3 em conformidade.

comm -1 -3 file2 file1

Isso exibe linhas do arquivo1 que não correspondem a nenhuma linha do arquivo2.

Por fim, existe join, um comando utilitário que executa uma junção de igualdade nos arquivos especificados. Sua opção -v também permite remover linhas comuns entre dois arquivos.

join -v1 -v2 file1 file2
Aakarsh Gupta
fonte
Todos estes já foram dados em outras respostas. Seu grep precisa de um -F, ou você obterá resultados estranhos quando as linhas parecerem regexps
The Archetypal Paul