Używanie 'diff' (lub czegokolwiek innego) do uzyskania różnic na poziomie znaków między plikami tekstowymi

91

Chciałbym użyć „diff”, aby uzyskać zarówno różnicę między wierszami, jak i różnicę znaków. Weźmy na przykład pod uwagę:

Plik 1

abcde
abc
abcccd

Plik 2

abcde
ab
abccc

Używając diff -u otrzymuję:

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

Jednak pokazuje mi tylko, że były to zmiany w tych liniach. Chciałbym zobaczyć coś takiego:

@@ -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

Masz mój dryf.

Teraz wiem, że mogę użyć innych silników, aby zaznaczyć / sprawdzić różnicę na określonej linii. Ale wolałbym użyć jednego narzędzia, które zrobi to wszystko.

VitalyB
źródło
2
per char diff jest szczególnie przydatne w przypadku tekstów CJK, w których do dzielenia na słowa nie stosuje się białych znaków.
把 友情 留 在 无 盐

Odpowiedzi:

75

Git ma słowo diff, a definiowanie wszystkich znaków jako słów skutecznie daje ci różnicę między znakami. Jednak zmiany nowej linii są ignorowane .

Przykład

Utwórz takie repozytorium:

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

Teraz zrób, git diff --word-diff=color --word-diff-regex=. master^ mastera otrzymasz:

git diff

Zwróć uwagę, jak zarówno dodania, jak i usunięcia są rozpoznawane na poziomie znaku, podczas gdy zarówno dodawanie, jak i usuwanie nowych linii są ignorowane.

Możesz także spróbować jednego z tych:

git diff --word-diff=plain --word-diff-regex=. master^ master
git diff --word-diff=porcelain --word-diff-regex=. master^ master
senf78
źródło
76
Nie musisz w ogóle tworzyć repozytorium, możesz po prostu dać git diff dowolne dwa pliki, w dowolnym miejscu w systemie plików i to działa. Twoje polecenie działa w ten sposób świetnie, więc dzięki! git diff --word-diff=color --word-diff-regex=. file1 file2
qwertzguy
1
To jest bardzo pomocne! Gdybym mógł, dałbym +1 raz jako programista i dwa razy więcej jako autor / pisarz. W przeciwieństwie do kodu, w którym linie są zwykle dość krótkie, podczas pisania artykułów / artykułów, każdy akapit ma zwykle postać długiej linii zawiniętej w słowa, a ta funkcja sprawia, że ​​różnice są faktycznie wizualnie przydatne.
mtraceur
29
Musiałem dodać --no-indexdo odpowiedzi @ qwertzguys powyżej, aby działała dla mnie poza repozytorium git. A więc:git diff --no-index --word-diff=color --word-diff-regex=. file1 file2
Nathan Bell
2
git diff nie działa w ustawieniach ogólnych: git diff --no-index --word-diff = color --word-diff-regex =. <(echo string1) <(echo string2) .. Nic, ale to działa: diff --color <(echo string1) <(echo string2).
mosh
1
@NathanBell Musiałem dodać również --no-indexwnętrze repozytorium
JShorthouse
32

Możesz użyć:

diff -u f1 f2 |colordiff |diff-highlight

zrzut ekranu

colordiffto pakiet Ubuntu. Możesz go zainstalować za pomocą sudo apt-get install colordiff.

diff-highlightpochodzi z gita (od wersji 2.9). Znajduje się w /usr/share/doc/git/contrib/diff-highlight/diff-highlight. Możesz go umieścić gdzieś w swoim $PATH.

zhanxw
źródło
6
colordiff jest również dostępny w homebrew na Maca:brew install colordiff
Emil Stenström
5
Na komputerze Mac można znaleźć diff-highlightw$(brew --prefix git)/share/git-core/contrib/diff-highlight/diff-highlight
StefanoP
2
W przypadku, gdy nie zainstalowano za pomocą git napar - diff-highlightmoże być również zainstalowany z pip Pythona - pip install diff-highlight(wolę to nawet jeśli jest zainstalowany git poprzez wywaru)
Yaron U.
22

Różnica w Pythonie jest asem, jeśli chcesz to zrobić programowo. Do użytku interaktywnego używam trybu diff vima (dość łatwy w użyciu: po prostu wywołaj vim za pomocą vimdiff a b). Od czasu do czasu używam Beyond Compare , który robi prawie wszystko, czego można oczekiwać od narzędzia porównywania.

Nie widziałem żadnego narzędzia wiersza poleceń, które robi to pożytecznie, ale jak zauważa Will, przykładowy kod difflib może pomóc.

Ned
źródło
1
Och ... Liczyłem na coś bardziej znormalizowanego (jak ukryty argument wiersza poleceń). Najgorsze jest to, że mam Beyond Compare 2 i obsługuje on nawet wyjście tekstowe do pliku / konsoli różnicy, ale nadal zawiera tylko różnice liniowe, a nie znaki różnicowe. Zajrzę się Pythonem, jeśli nikt nie ma nic innego.
VitalyB
6
+1 za wprowadzenie mnie do vimdiff. Zauważyłem, że domyślne kolory są nieczytelne, ale znalazłem rozwiązanie tego problemu na stackoverflow.com/questions/2019281/… .
undefined
18

Możesz użyć cmppolecenia w Solarisie:

cmp

Porównaj dwa pliki, a jeśli się różnią, pokaże pierwszy bajt i numer linii, gdzie się różnią.

Venkataramesh Kommoju
źródło
2
cmpjest również dostępny w (przynajmniej niektórych) dystrybucjach Linuksa.
Jeff Evans
7
Jest również dostępny na Mac OS X.
Eric R. Rath,
Znaki mogą składać się z wielu bajtów, a OP poprosił o wizualne porównanie.
Cees Timmerman
1
@CeesTimmerman: cmp umożliwia wizualne porównanie z flagą -l -b.
Smar
10

Python ma wygodną bibliotekę nazwaną, difflibktóra może pomóc odpowiedzieć na twoje pytanie.

Poniżej znajdują się dwa onelinery używające difflibróżnych wersji Pythona.

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()))'

Mogą się one przydać jako alias powłoki, który łatwiej jest poruszać się po .${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

I bardziej czytelna wersja do umieszczenia w samodzielnym pliku.

#!/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)
Pan Nieśmiertelny
źródło
Doskonałe wkładki. Byłoby miło mieć skondensowane wyjście, które ignoruje niezmienione wiersze.
aidan.plenert.macdonald
6
cmp -l file1 file2 | wc

Pracował dobrze dla mnie. Liczba znajdująca się po lewej stronie wyniku wskazuje liczbę znaków, które się różnią.

Chris Prince
źródło
1
Lub po prostu uzyskać numer z lewej strony:cmp -l file1 file2 | wc -l
Tony
5

Napisałem również własny skrypt, aby rozwiązać ten problem za pomocą algorytmu Najdłuższy wspólny podciąg.

Jest wykonywany jako taki

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

Wynik jest w html z czerwono-zielonymi kolorami. Większe pliki wykładniczo zajmują więcej czasu na przetworzenie, ale robi to prawdziwe porównanie znak po znaku bez sprawdzania linii po linii.

Joshua
źródło
Odkryłem, że JLDiff działa znacznie szybciej pod pypy.
Joshua,
4

Kolorowe wyjście na poziomie postaci diff

Oto, co możesz zrobić z poniższym skryptem i diff-highlight (który jest częścią git):

Kolorowy zrzut ekranu różnicowego

#!/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

( Podziękowania dla odpowiedzi @ retracile za sedwyróżnienie)

Tom Hale
źródło
Pokazuje dobrą różnicę na ekranie powłoki, ale jak mogę zobaczyć tę różnicę w GVim?
Hemant Sharma,
1
Co to jest naprawdę pytanie GVIM :). command | gvim -zrobisz, co chcesz.
Att Righ
Dla odniesienia diff-highlight wydaje się być częścią gitścieżki, ale nie jest na niej umieszczony. Jedna moja maszyna w tym życiu /usr/share/doc/git/contrib/diff-highlight.
Att Righ
uszkodzony link. Jak zainstalować diff-highlight. Nie wydaje się być w menedżerze pakietów.
Trevor Hickey
3

Difflib Pythona może to zrobić.

Dokumentacja zawiera przykładowy program wiersza poleceń .

Dokładny format nie jest taki, jak podałeś, ale byłoby proste albo przeanalizować wyjście w stylu ndiff, albo zmodyfikować przykładowy program w celu wygenerowania notacji.

Będzie
źródło
Dzięki! Przyjrzę się temu. Liczyłem na coś bardziej znormalizowanego (jak ukryty argument wiersza poleceń). Ale może nadal działać dobrze. Zajrzę się Pythonem, jeśli nikt nie ma czegoś bardziej standardowego (choć wydaje się, że nie).
VitalyB
2

Oto narzędzie do porównywania tekstu online: http://text-compare.com/

Może wyróżnić każdy inny znak i kontynuować porównywanie pozostałych.

gm2008
źródło
Wydaje się, że robi to różnice na poziomie liniowym bez opcji dla pojedynczych znaków. W jaki sposób porównujesz postacie?
Dragon
Ach; podkreśla postacie, które są różne. Ale nadal jest to poziom liniowy catdogi cat\ndogbędzie pasował dopierocat
Dragon
1

Myślę, że prostsze rozwiązanie jest zawsze dobrym rozwiązaniem. W moim przypadku poniższy kod bardzo mi pomaga. Mam nadzieję, że pomoże to komukolwiek innemu.

#!/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]

Możesz porównać dwa pliki o następującej składni na swoim ulubionym terminalu:

$ ./diff.py fileNumber1 fileNumber2
Miere
źródło
0

Jeśli przechowujesz swoje pliki w Git, możesz porównywać wersje za pomocą skryptu diff-highlight , który pokaże różne linie, z podświetlonymi różnicami.

Niestety działa to tylko wtedy, gdy liczba usuniętych wierszy jest zgodna z liczbą dodanych wierszy - istnieje kod pośredniczący, który oznacza, że ​​wiersze nie pasują, więc prawdopodobnie można to naprawić w przyszłości.

naught101
źródło
0

Nie jest to pełna odpowiedź, ale jeśli cmp -lwynik nie jest wystarczająco jasny, możesz użyć:

sed 's/\(.\)/\1\n/g' file1 > file1.vertical
sed 's/\(.\)/\1\n/g' file2 > file2.vertical
diff file1.vertical file2.vertical
sudo rm -rf slash
źródło
na OSX użyj `` sed 's / (.) / \ 1 \' $ '\ n / g' file1> file1.vertical sed 's / \ (. \) / \ 1 \' $ '\ n / g 'file2> file2.vertical ``
mmacvicar
0

Większość z tych odpowiedzi wspomina o użyciu diff-highlight , modułu Perla. Ale nie chciałem wymyślić, jak zainstalować moduł Perla. Więc wprowadziłem kilka drobnych zmian, aby był samodzielnym skryptem Perla.

Możesz go zainstalować za pomocą:

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

I użycie (jeśli masz Ubuntu colordiffwymienione w odpowiedzi zhanxw):

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

I użycie (jeśli nie):

▶ diff -u f1 f2 | DiffHighlight.pl
Alex Harvey
źródło