Comando Unix para encontrar linhas comuns em dois arquivos


179

Tenho certeza de que encontrei um comando unix que poderia imprimir as linhas comuns de dois ou mais arquivos. Alguém sabe o nome? Era muito mais simples que diff.


5
As respostas a esta pergunta não são necessariamente o que todos desejam, pois commrequer arquivos de entrada classificados. Se você quer apenas linha por linha comum, é ótimo. Mas se você quiser o que eu chamaria de "anti-diff", commnão faz o trabalho.
22812 Robert P. Goldman

@ RobertP.Goldman existe uma maneira de se tornar comum entre dois arquivos quando o arquivo1 contém um padrão parcial como o pr-123-xy-45arquivo2 ec11_orop_pr-123-xy-45.gz. Eu preciso file3 contendoec11_orop_pr-123-xy-45.gz
Chandan Choudhury

Veja este para classificar os ficheiros de texto linha por linha
y2k-shubham

Respostas:


216

O comando que você está procurando é comm. por exemplo:-

comm -12 1.sorted.txt 2.sorted.txt

Aqui:

-1 : suprime a coluna 1 (linhas exclusivas para 1.sorted.txt)

-2 : suprime a coluna 2 (linhas exclusivas para 2.sorted.txt)


27
Uso típico: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK

45
Enquanto comm precisa de arquivos classificados, você pode usar grep -f file1 file2 para obter as linhas comuns de ambos os arquivos.
ferdy

2
@ferdy (Repetir meu comentário da sua resposta, já que a sua é essencialmente uma resposta repetida postada como comentário) grepfaz algumas coisas estranhas que você não pode esperar. Especificamente, tudo 1.txtserá interpretado como uma expressão regular e não como uma sequência simples. Além disso, qualquer linha em branco 1.txtcorresponderá a todas as linhas 2.txt. Então grep, só funcionará em situações muito específicas. Você pelo menos gostaria de usar fgrep(ou grep -f), mas o problema da linha em branco provavelmente causará estragos nesse processo.
Christopher Schultz

11
Veja a resposta de ferdy abaixo e os comentários de Christopher Schultz e meus. TL; DR - uso . grep -F -x -f file1 file2
22815 Jonathan Leffler

1
@bapors: forneci uma pergunta e resposta auto-respondidas como Como obter a saída do commcomando em 3 arquivos separados? A resposta era grande demais para caber confortavelmente aqui.
27616 Jonathan Leffler

62

Para aplicar facilmente o comando comm a arquivos não classificados , use a substituição de processo do Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Portanto, os arquivos abc e def têm uma linha em comum, aquela com "132". Usando comm em arquivos não classificados:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

A última linha não produziu saída, a linha comum não foi descoberta.

Agora use comm em arquivos classificados, classificando os arquivos com substituição de processo:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Agora temos a linha 132!


2
então ... sort abc > abc.sorted, sort dev > def.sortede depois comm -12 abc.sorted def.sorted?
Nikana Reklawyks

1
@NikanaReklawyks E lembre-se de remover os arquivos temporários posteriormente e lidar com a limpeza em caso de erro. Em muitos cenários, a substituição do processo também será muito mais rápida, porque você pode evitar a E / S do disco desde que os resultados caibam na memória.
Tripleee

29

Para complementar o one-liner Perl, aqui está o seu awkequivalente:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Isso lerá todas as linhas da file1matriz arr[]e, em seguida, verificará se cada linha file2já existe na matriz (ou seja file1). As linhas encontradas serão impressas na ordem em que aparecem file2. Observe que a comparação in arrusa a linha inteira de file2como índice para a matriz, portanto, somente reportará correspondências exatas em linhas inteiras.


2
Essa é a resposta correta. Nenhum dos outros pode ser feito para funcionar em geral (eu não tentei perlos, porque). Graças a um milhão, Sra.
entonio 30/05

1
Preservar a ordem ao exibir as linhas comuns pode ser realmente útil em alguns casos que excluiriam a comunicação por causa disso.
Tuxayo

1
Caso alguém queira fazer o mesmo com base em uma determinada coluna, mas não saiba o que é awk, substitua os $ 0 por $ 5, por exemplo, para a coluna 5, para obter linhas compartilhadas em 2 arquivos com as mesmas palavras na coluna 5
FatihSarigol 31/01/19

24

Talvez você queira dizer comm?

Compare os arquivos classificados FILE1 e FILE2 linha por linha.

Sem opções, produza saída de três colunas. A coluna um contém linhas exclusivas para FILE1, a coluna dois contém linhas exclusivas para FILE2 e a coluna três contém linhas comuns aos dois arquivos.

O segredo para encontrar essas informações são as páginas de informações. Para programas GNU, eles são muito mais detalhados do que suas páginas de manual. Tente info coreutilse ele listará todos os pequenos utilitários úteis.


19

Enquanto

grep -v -f 1.txt 2.txt > 3.txt

fornece as diferenças de dois arquivos (o que está no 2.txt e não no 1.txt), você pode facilmente fazer um

grep -f 1.txt 2.txt > 3.txt

coletar todas as linhas comuns, o que deve fornecer uma solução fácil para o seu problema. Se você classificou os arquivos, você deve fazer isso comm. Saudações!


2
grepfaz algumas coisas estranhas que você não pode esperar. Especificamente, tudo 1.txtserá interpretado como uma expressão regular e não como uma sequência simples. Além disso, qualquer linha em branco 1.txtcorresponderá a todas as linhas 2.txt. Portanto, isso funcionará apenas em situações muito específicas.
Christopher Schultz

13
@ChristopherSchultz: É possível atualizar esta resposta para funcionar melhor usando as grepnotações POSIX , que são suportadas pelo grepencontrado nas variantes Unix mais modernas. Adicione -F(ou use fgrep) para suprimir expressões regulares. Adicione -x(para exato) para corresponder apenas a linhas inteiras.
22815 Jonathan Leffler

Por que devemos commusar os arquivos classificados?
amigos estão

2
O @UlysseBN commpode trabalhar com arquivos arbitrariamente grandes, desde que sejam ordenados, porque ele só precisa conter três linhas na memória (acho que o GNU commsaberia manter apenas um prefixo, se as linhas forem realmente longas). A grepsolução precisa manter todas as expressões de pesquisa na memória.
Tripleee

9

Se os dois arquivos ainda não foram classificados, você pode usar:

comm -12 <(sort a.txt) <(sort b.txt)

e funcionará, evitando a mensagem de erro comm: file 2 is not in sorted order ao fazê-lo comm -12 a.txt b.txt.


Você está certo, mas isso é essencialmente repetir outra resposta , que realmente não oferece nenhum benefício. Se você decidir responder a uma pergunta mais antiga que tenha respostas bem estabelecidas e corretas, adicionar uma nova resposta no final do dia pode não lhe dar crédito. Se você tiver alguma informação nova e distinta, ou estiver convencido de que as outras respostas estão erradas, adicione uma nova resposta, mas "mais uma resposta" fornecerá a mesma informação básica muito tempo depois que a pergunta for feita normalmente " você ganha muito crédito.
22617 Jonathan

Eu nem vi essa resposta @ JonathanLeffler porque essa parte estava no final da resposta, misturada com outros elementos de resposta antes. Embora a outra resposta seja mais precisa, acho que o benefício meu é que para alguém que deseja uma solução rápida, apenas duas linhas serão lidas. Às vezes, procuramos respostas detalhadas e, às vezes, temos pressa, e uma resposta pronta para colar de leitura rápida é boa.
Basj

Também não me importo com crédito / representante, não postei para esse fim.
Basj

1
Observe também que a sintaxe de substituição do processo <(command)não é portátil para o shell POSIX, embora funcione no Bash e em alguns outros.
Tripleee

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

isso está funcionando melhor do que o commcomando como ele procura cada linha de file1em file2que commcomparará somente se a linha nem file1é igual a linha nno file2.
Teriiehina 11/11

1
@teriiehina: Não; commsimplesmente não compara a linha N no arquivo1 com a linha N no arquivo2. É perfeitamente possível gerenciar uma série de linhas inseridas em qualquer arquivo (o que equivale a excluir uma série de linhas do outro arquivo, é claro). Apenas requer que as entradas estejam em ordem classificada.
22615 Jonathan Leffler

Melhor do que commrespostas, se alguém quiser manter a ordem. Melhor do que awkresponder se alguém não quiser duplicatas.
Tuxayo



3

Na versão limitada do Linux (como um QNAP (nas) em que eu estava trabalhando)):

  • comm não existia
  • grep -f file1 file2pode causar alguns problemas, como foi dito por @ChristopherSchultz, e o uso grep -F -f file1 file2foi muito lento (mais de 5 minutos - não foi concluído - mais de 2-3 segundos com o método abaixo em arquivos com mais de 20 MB)

Então aqui está o que eu fiz:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Se files.same.sorteddeve ter sido na mesma ordem que as originais, adicione esta linha pela mesma ordem que o arquivo1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

ou, pela mesma ordem que o arquivo2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

Apenas para referência, se alguém ainda estiver pensando em como fazer isso para vários arquivos, consulte a resposta vinculada a Localização de linhas correspondentes em muitos arquivos.


Combinando essas duas respostas ( ans1 e ans2 ), acho que você pode obter o resultado que precisa sem classificar os arquivos:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Simplesmente salve, conceda direitos de execução ( chmod +x compareFiles.sh) e execute-o. Ele pegará todos os arquivos presentes no diretório de trabalho atual e fará uma comparação entre todos, deixando no arquivo "matching_lines" o resultado.

Coisas a serem melhoradas:

  • Ignorar diretórios
  • Evite comparar todos os arquivos duas vezes (arquivo1 x arquivo2 e arquivo2 x arquivo1).
  • Talvez adicione o número da linha ao lado da string correspondente

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Isso deve servir.


1
Você provavelmente deve usar rm -f file3.txtse deseja excluir o arquivo; isso não relatará nenhum erro se o arquivo não existir. OTOH, não seria necessário se o seu script simplesmente ecoasse na saída padrão, deixando o usuário do script escolher para onde a saída deveria ir. Por fim, você provavelmente desejaria usar $1e $2(argumentos de linha de comando) em vez de nomes de arquivos fixos ( file1.oute file2.out). Isso deixa o algoritmo: será lento. Vai ler file2.outuma vez para cada linha file1.out. Ficará lento se os arquivos forem grandes (digamos, vários kilobytes).
Jonathan Leffler

Embora isso possa funcionar nominalmente se você tiver entradas que não contenham metacaracteres de shell (dica: veja quais avisos você recebe do shellcheck.net ), essa abordagem ingênua é terrivelmente ineficiente. Uma ferramenta como a grep -Fque lê um arquivo na memória e, em seguida, faz uma única passagem sobre o outro, evita repetidas repetições nos dois arquivos de entrada.
Tripleee
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.