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.

muito php
fonte
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)

Jonathan Leffler
fonte
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!

Stephan Wehner
fonte
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.

Tatjana Heuser
fonte
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.

Johannes Schaub - litb
fonte
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!

ferdy
fonte
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.

Basj
fonte
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
user2592005
fonte
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
Uma explicação está aqui: stackoverflow.com/questions/17552789/…
Chris Koknat
5
awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2
RS John
fonte
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
Master DJon
fonte
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
akarpovsky
fonte
-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.

Alan Joseph
fonte
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