Compare dois arquivos linha por linha e gere a diferença em outro arquivo

121

Quero comparar o arquivo1 com o arquivo2 e gerar um arquivo3 que contém as linhas do arquivo1 que não estão presentes no arquivo2.

Balualways
fonte
Tentei o diff, mas ele gera alguns números e outros símbolos na frente de linhas diferentes, o que torna difícil para mim comparar os arquivos.
Dom

Respostas:

216

diff (1) não é a resposta, mas comm (1) é.

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

assim

comm -2 -3 file1 file2 > file3

Os arquivos de entrada devem ser classificados. Se não forem, classifique-os primeiro. Isso pode ser feito com um arquivo temporário ou ...

comm -2 -3 <(sort file1) <(sort file2) > file3

desde que seu shell suporte substituição de processo (bash faz).

sorpigal
fonte
1
Lembre-se de que dois arquivos devem ser classificados e são únicos
andy
6
Você pode agrupar as opções:comm -23
Paolo M
O que significa "classificado"? Que as linhas têm a mesma ordem? Então, provavelmente, é adequado para a maioria dos casos de uso - como em, verificar quais linhas foram adicionadas comparando com uma versão anterior com backup. Se as linhas recém-adicionadas não podem estar entre as linhas existentes, isso é mais um problema.
Egor Hans
@EgorHans: se o arquivo tiver, por exemplo, linhas contendo inteiros como "3 \ n1 \ n3 \ n2 \ n", as linhas devem primeiro ser reordenadas em ordem crescente ou decrescente, por exemplo, "\ 1 \ n2 \ n3 \ n3 \ n" com duplicatas adjacente. Isso é "classificado" e ambos os arquivos devem ser classificados de maneira semelhante. Quando o arquivo mais recente tem novas linhas, não importa se elas estão "entre as linhas existentes" porque depois da classificação não estão, elas estão em ordem de classificação.
sorpigal
48

O utilitário Unix diffé feito exatamente para esse propósito.

$ diff -u file1 file2 > file3

Consulte o manual e a Internet para opções, diferentes formatos de saída, etc.

Thanatos
fonte
8
Isso não cumpre o trabalho solicitado; ele insere um monte de caracteres extras, mesmo com o uso de opções de linha de comando sugeridas em outras respostas.
xenocyon
20

Considere o seguinte:
arquivo a.txt:

abcd
efgh

arquivo b.txt:

abcd

Você pode encontrar a diferença com:

diff -a --suppress-common-lines -y a.txt b.txt

O resultado será:

efgh 

Você pode redirecionar a saída em um arquivo de saída (c.txt) usando:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

Isso vai responder à sua pergunta:

"... que contém as linhas no arquivo1 que não estão presentes no arquivo2."

Neilvert Noval
fonte
2
Existem duas limitações para esta resposta: (1) só funciona para linhas curtas (menos de 80 caracteres por padrão, embora isso possa ser modificado) e, mais importante, (2) adiciona um "<" no final de cada linha que deve ser retirada com outro programa (por exemplo, awk, sed).
sergut
Em muitos casos, você também desejará usar -d, o que fará diffo possível para encontrar a menor diferença possível. -i, -E, -w, -BE --suppress-blank-emptypode também ser útil, ocasionalmente, embora não sempre. Se você não sabe o que se encaixa no seu caso de uso, tente diff --helpprimeiro (o que geralmente é uma boa ideia quando você não sabe o que um comando pode fazer).
Egor Hans
Além disso, usando --line-format =% L, você evita que o diff gere quaisquer caracteres extras (pelo menos, a ajuda diz que funciona assim, ainda prestes a experimentá-lo).
Egor Hans
Além disso, isso é mais curto e parece funcionar da mesma forma stackoverflow.com/a/27667185/1179925
mrgloom
8

Às vezes diffé o utilitário de que você precisa, mas às vezes joiné mais apropriado. Os arquivos precisam ser pré-classificados ou, se você estiver usando um shell que suporte a substituição de processos, como bash, ksh ou zsh, você pode fazer a classificação em tempo real.

join -v 1 <(sort file1) <(sort file2)
Pausado até novo aviso.
fonte
Você deveria ganhar uma medalha por isso! Era exatamente o que eu estava procurando nas últimas 2 horas
Zatarra
7

Experimentar

sdiff file1 file2

Normalmente funciona muito melhor na maioria dos casos para mim. Você pode querer classificar os arquivos antes, se a ordem das linhas não for importante (por exemplo, alguns arquivos de configuração de texto).

Por exemplo,

sdiff -w 185 file1.cfg file2.cfg
Tagar
fonte
1
Belo utilitário! Eu amo como isso marca as linhas de diferenciação. Torna muito mais fácil comparar configurações. Isso junto com o tipo é uma combinação mortal (por exemplo sdiff <(sort file1) <(sort file2))
jmagnusson
3

Se você precisa resolver isso com coreutils, a resposta aceita é boa:

comm -23 <(sort file1) <(sort file2) > file3

Você também pode usar sd (stream diff), que não requer classificação nem substituição de processo e suporta fluxos infinitos, assim:

cat file1 | sd 'cat file2' > file3

Provavelmente não é um grande benefício neste exemplo, mas ainda considere isso; em alguns casos você não poderá usar commnem grep -Fnem diff.

Aqui está uma postagem de blog que escrevi sobre diffing streams no terminal, que apresenta o sd.

mlg
fonte
3

Ainda assim, nenhuma grepsolução?

  • linhas que existem apenas no arquivo 2:

    grep -Fxvf file1 file2 > file3
  • linhas que existem apenas no arquivo 1:

    grep -Fxvf file2 file1 > file3
  • linhas que existem em ambos os arquivos:

    grep -Fxf file1 file2 > file3
αғsнιη
fonte
2

Muitas respostas já, mas nenhuma delas IMHO perfeito. A resposta de Thanatos deixa alguns caracteres extras por linha e a resposta de Sorpigal exige que os arquivos sejam classificados ou pré-classificados, o que pode não ser adequado em todas as circunstâncias.

Acho que a melhor maneira de obter as linhas que são diferentes e nada mais (não caracteres extras, sem re-ordenação) é uma combinação de diff, grepeawk (ou similar).

Se as linhas não contêm nenhum "<", uma linha curta pode ser:

diff urls.txt* | grep "<" | sed 's/< //g'

mas isso removerá todas as instâncias de "<" (menos que, espaço) das linhas, o que nem sempre está OK (por exemplo, código-fonte). A opção mais segura é usar o awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

Este one-liner diffs ambos os arquivos, então filtra a saída do estilo ed do diff, então remove o "<" que o diff adiciona. Isso funciona mesmo se as linhas contiverem alguns "<".

Sargute
fonte
1
comm não requer classificação (em versões mais recentes?) - basta usar --nocheck-order. Eu uso muito isso ao manipular csvs da CLI
ak5
2

Eu não sou ninguém surpreso mencionado diff -ypara produzir uma saída de side-by-side , por exemplo:

diff -y file1 file2 > file3

E em file3(as diferentes linhas têm um símbolo |no meio):

same     same
diff_1 | diff_2
xtluo
fonte
1

Use o utilitário Diff e extraia apenas as linhas que começam com <na saída

Capslockk
fonte
0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

Tentei quase todas as respostas neste tópico, mas nenhuma foi completa. Depois de algumas trilhas acima, uma funcionou para mim. diff lhe dará diferença, mas com alguns charas especiais indesejados. onde suas linhas de diferença reais começam com '>'. então o próximo passo é fazer o grep das linhas começar com '>' e, em seguida, remover as mesmas com sed .

tollin jose
fonte
1
Esta é uma má ideia. Você também precisaria modificar as linhas começando com <. Você verá isso se trocar a ordem dos arquivos de entrada. Mesmo se você fizesse isso, você iria querer omitir grepusando mais sed: `diff a1 a2 | sed '/> / s ///' `Isso ainda pode quebrar linhas contendo >ou <na situação certa e ainda deixa linhas extras descrevendo os números das linhas. Se você queria experimentar esta abordagem uma maneira melhor seria: diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'.
sorpigal
0

Você pode usar diffcom a seguinte formatação de saída:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format='', desabilite a saída para arquivo1 se a linha for diferente compare em arquivo2.
--unchanged-line-format='', desative a saída se as linhas forem iguais.

αғsнιη
fonte