Usando 'diff' (ou qualquer outra coisa) para obter diferenças em nível de caractere entre arquivos de texto


91

Eu gostaria de usar 'diff' para obter uma diferença de linha e de caractere. Por exemplo, considere:

Arquivo 1

abcde
abc
abcccd

Arquivo 2

abcde
ab
abccc

Usando diff -u eu obtenho:

@@ -1,3 +1,3 @@
 abcde
-abc
-abcccd
\ No newline at end of file
+ab
+abccc
\ No newline at end of file

No entanto, isso só me mostra que houve mudanças nessas linhas. O que eu gostaria de ver é algo como:

@@ -1,3 +1,3 @@
 abcde
-ab<ins>c</ins>
-abccc<ins>d</ins>
\ No newline at end of file
+ab
+abccc
\ No newline at end of file

Você me entende.

Agora, eu sei que posso usar outros motores para marcar / verificar a diferença em uma linha específica. Mas prefiro usar uma ferramenta que faça tudo isso.


2
per char diff é especialmente útil quando se trata de textos CJK, onde nenhum espaço em branco é aplicado para a divisão de palavras.
把 友情 留 在 无 盐

Respostas:


72

Git tem uma palavra diff, e definir todos os caracteres como palavras efetivamente dá a você uma diff de caractere. No entanto, as alterações de nova linha são ignoradas .

Exemplo

Crie um repositório como este:

mkdir chardifftest
cd chardifftest
git init
echo -e 'foobarbaz\ncatdog\nfox' > file
git add -A; git commit -m 1
echo -e 'fuobArbas\ncat\ndogfox' > file
git add -A; git commit -m 2

Agora, faça git diff --word-diff=color --word-diff-regex=. master^ mastere você obterá:

git diff

Observe como as adições e exclusões são reconhecidas no nível do caractere, enquanto as adições e exclusões de novas linhas são ignoradas.

Você também pode tentar um destes:

git diff --word-diff=plain --word-diff-regex=. master^ master
git diff --word-diff=porcelain --word-diff-regex=. master^ master

73
Você não precisa criar um repo, você pode simplesmente fornecer ao git diff quaisquer dois arquivos, em qualquer lugar em seu sistema de arquivos e ele funcionará. Seu comando funciona muito bem para mim dessa forma, então, obrigado! git diff --word-diff=color --word-diff-regex=. file1 file2
qwertzguy

1
Isso é profundamente útil! Marcaria +1 uma vez como desenvolvedor de software e +1 duas vezes mais como autor / escritor se pudesse. Ao contrário do código, onde as linhas tendem a ser razoavelmente curtas, ao escrever artigos / histórias, cada parágrafo tende a assumir a forma de uma longa linha quebrada, e esse recurso torna as diferenças visualmente úteis.
mtraceur

28
Eu precisava adicionar --no-indexa resposta @qwertzguys acima para fazê-lo funcionar para mim fora de um repositório git. Então:git diff --no-index --word-diff=color --word-diff-regex=. file1 file2
Nathan Bell

2
git diff não funciona na configuração geral: git diff --no-index --word-diff = cor --word-diff-regex =. <(echo string1) <(echo string2) .. Nada, mas funciona: diff --cor <(echo string1) <(echo string2).
mosh

1
@NathanBell, eu precisava adicionar --no-indexdentro de um repo também
JShorthouse

32

Você pode usar:

diff -u f1 f2 |colordiff |diff-highlight

captura de tela

colordiffé um pacote Ubuntu. Você pode instalá-lo usando sudo apt-get install colordiff.

diff-highlighté do git (desde a versão 2.9). Ele está localizado em /usr/share/doc/git/contrib/diff-highlight/diff-highlight. Você pode colocá-lo em algum lugar do seu $PATH.


6
colordiff também está disponível no homebrew para Mac:brew install colordiff
Emil Stenström

5
No Mac, você pode encontrar diff-highlightem$(brew --prefix git)/share/git-core/contrib/diff-highlight/diff-highlight
StefanoP

2
Caso você não tenha instalado o git usando o brew - diff-highlighttambém pode ser instalado com o pip do python - pip install diff-highlight(eu prefiro mesmo que o git seja instalado via brew)
Yaron U.

21

O difflib do Python é ace se você quiser fazer isso programaticamente. Para uso interativo, eu uso o modo diff do vim (fácil de usar: basta invocar o vim com vimdiff a b). Ocasionalmente, também uso o Beyond Compare , que faz praticamente tudo o que você poderia esperar de uma ferramenta diff.

Não vejo nenhuma ferramenta de linha de comando que faça isso de forma útil, mas, como Will observa, o código de exemplo difflib pode ajudar.


1
Oh .. Eu estava esperando algo mais padronizado (como um argumento de linha de comando oculto). A coisa mais ruim é que eu tenho Beyond Compare 2 e ainda suporta saída de texto para arquivo / console do diff, mas ainda inclui apenas line-diffs e não char-diffs. Vou pesquisar em python se ninguém tiver mais nada.
VitalyB

6
1 por me apresentar ao vimdiff. Achei as cores padrão ilegíveis, mas encontrei uma solução para isso em stackoverflow.com/questions/2019281/… .
indefinido,

17

Você pode usar o cmpcomando no Solaris:

cmp

Compare dois arquivos e, se eles forem diferentes, informa ao primeiro byte e ao número da linha onde eles diferem.


2
cmptambém está disponível em (pelo menos algumas) distribuições Linux.
Jeff Evans

7
Também está disponível no Mac OS X.
Eric R. Rath

Os caracteres podem consistir em bytes múltiplos e OP pede uma comparação visual.
Cees Timmerman

1
@CeesTimmerman: cmp permite comparação visual, com bandeira -l -b.
Smar

9

Python tem uma biblioteca conveniente chamada difflibque pode ajudar a responder sua pergunta.

Abaixo estão dois oneliners usando difflibpara diferentes versões de python.

python3 -c 'import difflib, sys; \
  print("".join( \
    difflib.ndiff( \ 
      open(sys.argv[1]).readlines(),open(sys.argv[2]).readlines())))'
python2 -c 'import difflib, sys; \
  print "".join( \
    difflib.ndiff( \
      open(sys.argv[1]).readlines(), open(sys.argv[2]).readlines()))'

Eles podem ser úteis como um alias de shell, que é mais fácil de mover com o seu .${SHELL_NAME}rc.

$ alias char_diff="python2 -c 'import difflib, sys; print \"\".join(difflib.ndiff(open(sys.argv[1]).readlines(), open(sys.argv[2]).readlines()))'"
$ char_diff old_file new_file

E uma versão mais legível para colocar em um arquivo autônomo.

#!/usr/bin/env python2
from __future__ import with_statement

import difflib
import sys

with open(sys.argv[1]) as old_f, open(sys.argv[2]) as new_f:
    old_lines, new_lines = old_f.readlines(), new_f.readlines()
diff = difflib.ndiff(old_lines, new_lines)
print ''.join(diff)

Excelente forros. Seria bom ter uma saída condensada que ignorasse as linhas inalteradas.
aidan.plenert.macdonald

6
cmp -l file1 file2 | wc

Funcionou bem para mim. O número mais à esquerda do resultado indica o número de caracteres que diferem.


1
Ou apenas para obter o número mais à esquerda:cmp -l file1 file2 | wc -l
Tony

5

Também escrevi meu próprio script para resolver esse problema usando o algoritmo de subsequência comum Longest.

É executado como tal

JLDiff.py a.txt b.txt out.html

O resultado é em html com coloração vermelha e verde. Arquivos maiores levam exponencialmente mais tempo para serem processados, mas isso faz uma comparação verdadeira de caractere por caractere sem verificar linha por linha primeiro.


Eu descobri que o JLDiff é executado muito mais rápido no pypy.
Joshua

4

Colorido, personagem de nível diff ouput

Aqui está o que você pode fazer com o script abaixo e o diff-realce (que faz parte do git):

Captura de tela de diferenças coloridas

#!/bin/sh -eu

# Use diff-highlight to show word-level differences

diff -U3 --minimal "$@" |
  sed 's/^-/\x1b[1;31m-/;s/^+/\x1b[1;32m+/;s/^@/\x1b[1;34m@/;s/$/\x1b[0m/' |
  diff-highlight

(Crédito para a resposta de @retracile pelo seddestaque)


Ele mostra um bom diff na tela do shell, mas como vejo esse diff no GVim ??
Hemant Sharma

1
O que é realmente uma questão gvim :). command | gvim -vai fazer o que você quiser.
Att Righ

Para referência, o destaque diferencial parece estar incluído como parte, gitmas não colocado em seu caminho. Uma máquina em que vive /usr/share/doc/git/contrib/diff-highlight.
Att Righ

link quebrado. Como faço para instalar o diff-destaque. Não parece estar em um gerenciador de pacotes.
Trevor Hickey

3

O difflib do Python pode fazer isso.

A documentação inclui um programa de linha de comando de exemplo para você.

O formato exato não é o especificado, mas seria simples analisar a saída no estilo ndiff ou modificar o programa de exemplo para gerar sua notação.


Obrigado! Vou dar uma olhada nisso. Eu esperava algo mais padronizado (como um argumento de linha de comando oculto). Mas pode funcionar bem ainda. Vou olhar para o python se ninguém tiver nada mais padrão (embora pareça que não).
VitalyB

2

Aqui está uma ferramenta de comparação de texto online: http://text-compare.com/

Ele pode destacar cada caractere diferente e continua a comparar o resto.


Isso parece fazer diferenças em nível de linha sem opção para caracteres únicos. Como você consegue comparar personagens?
Dragon

Ah; ele destaca personagens que são diferentes. Mas ainda está no nível de linha catdoge cat\ndogsó corresponderá emcat
Dragon

1

Acho que a solução mais simples é sempre uma boa solução. No meu caso, o código abaixo me ajuda muito. Espero que ajude mais alguém.

#!/bin/env python

def readfile( fileName ):
    f = open( fileName )
    c = f.read()
    f.close()
    return c

def diff( s1, s2 ):
    counter=0
    for ch1, ch2 in zip( s1, s2 ):
        if not ch1 == ch2:
            break
        counter+=1
    return counter < len( s1 ) and counter or -1

import sys

f1 = readfile( sys.argv[1] )
f2 = readfile( sys.argv[2] )
pos = diff( f1, f2 )
end = pos+200

if pos >= 0:
    print "Different at:", pos
    print ">", f1[pos:end]
    print "<", f2[pos:end]

Você pode comparar dois arquivos com a seguinte sintaxe em seu terminal favorito:

$ ./diff.py fileNumber1 fileNumber2

0

Se você mantiver seus arquivos no Git, você pode diferenciar entre as versões com o script diff-highlight , que mostrará diferentes linhas, com as diferenças destacadas.

Infelizmente, isso só funciona quando o número de linhas removidas corresponde ao número de linhas adicionadas - há um código de stub para quando as linhas não correspondem, portanto, presumivelmente, isso poderá ser corrigido no futuro.


0

Não é uma resposta completa, mas se cmp -la saída não for clara o suficiente, você pode usar:

sed 's/\(.\)/\1\n/g' file1 > file1.vertical
sed 's/\(.\)/\1\n/g' file2 > file2.vertical
diff file1.vertical file2.vertical

no OSX use `` `sed 's / (.) / \ 1 \' $ '\ n / g' arquivo1> arquivo1.vertical sed 's / \ (. \) / \ 1 \' $ '\ n / g 'file2> file2.vertical `` `
mmacvicar de

0

A maioria dessas respostas menciona o uso de diff-realce , um módulo Perl. Mas eu não queria descobrir como instalar um módulo Perl. Portanto, fiz algumas pequenas alterações para que fosse um script Perl autocontido.

Você pode instalá-lo usando:

▶ curl -o /usr/local/bin/DiffHighlight.pl \
   https://raw.githubusercontent.com/alexharv074/scripts/master/DiffHighlight.pl

E o uso (se você tiver o Ubuntu colordiffmencionado na resposta de zhanxw):

▶ diff -u f1 f2 | colordiff | DiffHighlight.pl

E o uso (se você não fizer):

▶ diff -u f1 f2 | DiffHighlight.pl
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.