Diferença de dois arquivos grandes

14

Eu tenho "test1.csv" e contém

200,400,600,800
100,300,500,700
50,25,125,310

e test2.csv e contém

100,4,2,1,7
200,400,600,800
21,22,23,24,25
50,25,125,310
50,25,700,5

agora

diff test2.csv test1.csv > result.csv

é diferente de

diff test1.csv test2.csv > result.csv

Eu não sei qual é a ordem correta, mas eu quero outra coisa, ambos os comandos acima irão mostrar algo como

2 > 100,4,2,1,7
   3 2,3c3,5
   4 < 100,300,500,700
   5 < 50,25,125,310
   6 \ No newline at end of file
   7 ---
   8 > 21,22,23,24,25
   9 > 50,25,125,310

Eu quero mostrar apenas a diferença, portanto o resultado.csv deve ser assim

100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Eu tentei diff -q e diff -s , mas eles não fizeram o truque. Ordem não importa, o que importa é que eu quero ver apenas a diferença, não > nem < nem espaço em branco.

grep -FvF fez o truque em arquivos menores, não em arquivos grandes

primeiro arquivo contém mais de 5 milhões de linhas, o segundo arquivo contém 1300.

so results.csv deve resultar em ~ 4.998.700 linhas

Eu também tentei grep -F -x -v -f , o que não funcionou.

    
por Lynob 23.06.2015 / 23:32

5 respostas

20

Parece um trabalho para comm :

$ comm -3 <(sort test1.csv) <(sort test2.csv)
100,300,500,700
    100,4,2,1,7
    21,22,23,24,25
    50,25,700,5

Como explicado em man comm :

   -1     suppress column 1 (lines unique to FILE1)

   -2     suppress column 2 (lines unique to FILE2)

   -3     suppress column 3 (lines that appear in both files)

Portanto, o -3 significa que apenas as linhas exclusivas de um dos arquivos serão impressas. No entanto, esses são recuados de acordo com o arquivo em que foram encontrados. Para remover a guia, use:

$ comm -3 <(sort test1.csv) <(sort test2.csv) | tr -d '\t'
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Nesse caso, você nem precisa classificar os arquivos e pode simplificar os itens acima para:

comm -3 test1.csv test2.csv | tr -d '\t' > difference.csv
    
por terdon 24.06.2015 / 00:23
6

Usando grep com bash substituição de processo:

$ cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv)
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Para salvar a saída como results.csv :

cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv) >results.csv
  • <() é o bash padrão de substituição do processo

  • grep -vFf test2.csv test1.csv encontrará as linhas exclusivas apenas para test1.csv

  • grep -vFf test1.csv test2.csv encontrará as linhas exclusivas apenas para test2.csv

  • Finalmente, estamos resumindo os resultados por cat

Ou como Oli sugeriu , você também pode usar o agrupamento de comandos:

$ { grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv; }
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Ou apenas execute um após o outro, já que eles estão escrevendo para o STDOUT, eles serão adicionados:

$ grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5
    
por heemayl 23.06.2015 / 23:47
4

Se a ordem das linhas não for relevante, use awk ou perl :

awk '{seen[$0]++} END {for (i in seen) {if (seen[i] == 1) {print i}}}' 1.csv 2.csv

Use grep para obter as linhas comuns e filtrá-las:

grep -hxvFf <(grep -Fxf 1.csv 2.csv) 1.csv 2.csv

O grep interno obtém as linhas comuns, então o grep externo encontra linhas que não correspondem a essas linhas comuns.

    
por muru 23.06.2015 / 23:53
4

Use as opções --*-line-format=... de diff

Você pode informar a diff exatamente o que precisa, conforme explicado abaixo:

diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' f1.txt f2.txt

É possível especificar a saída do diff de uma forma muito detalhada, semelhante a um formato de número printf .

As linhas do primeiro arquivo, test1.csv são chamadas de linhas "antigas", e as linhas do segundo, test2.csv , são "novas" linhas. Isso faz sentido quando diff é usado para ver o que mudou em um arquivo.

As opções de que precisamos são aquelas que definem o formato de linhas "antigas", linhas "novas" e linhas "inalteradas".
Os formatos que precisamos são muito simples:
Para as linhas alteradas, novas e antigas, queremos produzir apenas o texto das linhas. %L é o símbolo de formato para o texto da linha.
Para as linhas inalteradas, queremos mostrar nada.

Com isso, podemos escrever opções como --old-line-format='%L' e juntar tudo usando seus dados de exemplo:

$ diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' test1.csv test2.csv
100,4,2,1,7
100,300,500,700
21,22,23,24,25
50,25,700,5


Notas sobre o desempenho

Como os arquivos têm tamanho diferente, tente trocar os arquivos de entrada se isso não for importante, pode ser que o funcionamento interno de diff consiga lidar com uma maneira melhor que a outra. Melhor é precisar de menos memória ou menos computação.

Existe uma opção de otimização para usar diff com arquivos grandes: --speed-large-files . Ele usa suposições sobre a estrutura do arquivo, portanto não está claro se isso ajuda no seu caso, mas vale a pena tentar.

As opções de formato estão descritas na man diff em --LTYPE-line-format=LFMT .

    
por Volker Siegel 24.06.2015 / 08:59
3

Como o pedido não precisa ser preservado, simplesmente:

sort test1.csv test2.csv | uniq -u
  • sort test1.csv test2.csv : mescla e classifica test1.csv e test2.csv
  • uniq -u : imprime apenas as linhas que não têm duplicado
por kos 24.06.2015 / 00:14

Tags