Использование 'diff' (или чего-то еще) для получения различий на уровне символов между текстовыми файлами


93

Я хотел бы использовать 'diff', чтобы получить разницу между строками и символами. Например, рассмотрим:

Файл 1

abcde
abc
abcccd

Файл 2

abcde
ab
abccc

Используя diff -u, я получаю:

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

Однако это только показывает мне, что были изменения в этих строках. Я бы хотел увидеть что-то вроде:

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

Вы меня поняли.

Теперь я знаю, что могу использовать другие движки, чтобы отмечать / проверять разницу в определенной строке. Но я бы предпочел использовать один инструмент, который все это делает.


2
per char diff особенно полезен, когда речь идет о текстах CJK, где для разделения слов не используются пробелы.
把 友情 留 在 无 盐

Ответы:


76

В Git есть слово diff, и определение всех символов как слов фактически дает вам различие символов. Однако изменения новой строки игнорируются .

пример

Создайте такой репозиторий:

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

А теперь сделайте, git diff --word-diff=color --word-diff-regex=. master^ masterи вы получите:

git diff

Обратите внимание на то, что и добавления, и удаления распознаются на уровне символа, в то время как добавление и удаление новых строк игнорируются.

Вы также можете попробовать одно из следующих:

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

76
Вам вообще не нужно создавать репо, вы можете просто предоставить git diff любые два файла в любом месте вашей файловой системы, и он работает. Таким образом, ваша команда отлично работает для меня, поэтому спасибо! git diff --word-diff=color --word-diff-regex=. file1 file2
qwertzguy

1
Это очень полезно! Я бы +1 раз как разработчик программного обеспечения и +1 дважды как автор / писатель, если бы мог. В отличие от кода, где строки обычно достаточно короткие, при написании статей / рассказов каждый абзац имеет тенденцию принимать форму длинной строки с переносом слов, и эта функция делает различия действительно визуально полезными.
mtraceur

29
Мне нужно было добавить --no-indexответ @ qwertzguys выше, чтобы заставить его работать для меня вне репозитория git. Итак:git diff --no-index --word-diff=color --word-diff-regex=. file1 file2
Натан Белл

2
git diff не работает в общих настройках: git diff --no-index --word-diff = color --word-diff-regex =. <(echo string1) <(echo string2) .. Ничего, но это работает: diff --color <(echo string1) <(echo string2).
мош

1
@NathanBell Мне тоже нужно было добавить --no-indexв репо
JShorthouse

32

Ты можешь использовать:

diff -u f1 f2 |colordiff |diff-highlight

снимок экрана

colordiffэто пакет Ubuntu. Вы можете установить его, используя sudo apt-get install colordiff.

diff-highlightвзято из git (начиная с версии 2.9). Он расположен в /usr/share/doc/git/contrib/diff-highlight/diff-highlight. Вы можете поместить его где-нибудь в свой $PATH.


6
colordiff также доступен на homebrew для Mac:brew install colordiff
Эмиль Стенстрём,

5
На Mac вы можете найти diff-highlightв$(brew --prefix git)/share/git-core/contrib/diff-highlight/diff-highlight
StefanoP

2
Если вы не установили git с помощью brew - diff-highlightтакже можно установить с помощью python pip - pip install diff-highlight(я предпочитаю его, даже если git установлен с помощью brew)
Ярон У.

22

Если вы хотите делать это программно, то вам подойдет Python difflib . Для интерактивного использования я использую vim режим diff (достаточно простой в использовании: просто вызовите vim с помощью vimdiff a b). Я также иногда использую Beyond Compare , который делает почти все, на что вы можете надеяться от инструмента сравнения.

Я не видел никакого инструмента командной строки, который бы делал это с пользой, но, как отмечает Уилл, пример кода diffflib может помочь.


1
Ох .. Я надеялся на что-то более стандартизованное (например, скрытый аргумент командной строки). Самое проклятое, что у меня есть Beyond Compare 2, и он даже поддерживает вывод текста в файл / консоль diff, но по-прежнему включает только line-diffs, а не char-diffs. Я посмотрю на python, если больше ни у кого нет.
VitalyB

6
+1 за то, что познакомил меня с vimdiff. Я обнаружил, что цвета по умолчанию нечитаемы, но нашел решение для этого на stackoverflow.com/questions/2019281/… .
undefined

18

Вы можете использовать cmpкоманду в Solaris:

cmp

Сравните два файла и, если они различаются, сообщит первый байт и номер строки, где они различаются.


2
cmpтакже доступен (по крайней мере, в некоторых) дистрибутивах Linux.
Джефф Эванс,

7
Он также доступен в Mac OS X.
Эрик Р. Рат,

Символы могут состоять из нескольких байтов, и OP запрашивает визуальное сравнение.
Сес Тиммерман,

1
@CeesTimmerman: cmp позволяет визуальное сравнение с флагом -l -b.
Смар

10

У Python есть удобная библиотека с именем, difflibкоторая может помочь ответить на ваш вопрос.

Ниже приведены два oneliner'а, которые используются difflibдля разных версий 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()))'

Они могут пригодиться в качестве псевдонима оболочки, который легче перемещать с вашим .${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

И более читабельную версию поместить в отдельный файл.

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

Отличные лайнеры. Было бы неплохо получить сжатый вывод, игнорирующий неизмененные строки.
aidan.plenert.macdonald

6
cmp -l file1 file2 | wc

Хорошо сработало для меня. Крайний левый номер результата указывает количество различающихся символов.


1
Или просто взять крайний левый номер:cmp -l file1 file2 | wc -l
Тони

5

Я также написал свой собственный сценарий для решения этой проблемы с использованием алгоритма самой длинной общей подпоследовательности.

Он выполнен как таковой

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

Результат - HTML с красно-зеленой окраской. Для обработки больших файлов экспоненциально требуется больше времени, но при этом выполняется истинное сравнение символов без проверки сначала построчно.


Я обнаружил, что JLDiff работает намного быстрее под pypy.
Джошуа

4

Цветной вывод на уровне персонажа diff

Вот что вы можете сделать с помощью приведенного ниже скрипта и функции diff-highlight (которая является частью git):

Цветной скриншот различий

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

(Кредит на ответ @retracile за sedвыделение)


Он показывает хорошую разницу на экране оболочки, но как мне увидеть эту разницу в GVim ??
Хемант Шарма

1
Что это на самом деле вопрос gvim :). command | gvim -буду делать то, что ты хочешь.
Att Righ

Для справки, похоже, что diff-highlight включена как часть, gitно не помещается на ваш путь. На одной из моих машин эта живет /usr/share/doc/git/contrib/diff-highlight.
Атт Риг

неработающей ссылке. Как установить diff-highlight. Не похоже, чтобы в диспетчере пакетов.
Trevor Hickey

3

Диффлиб Python может это сделать.

Документация включает в себя пример программы командной строки для вас.

Точный формат не такой, как вы указали, но было бы просто либо проанализировать вывод в стиле ndiff, либо изменить пример программы для создания вашей нотации.


Благодарность! Я изучу это. Я надеялся на что-то более стандартизованное (например, скрытый аргумент командной строки). Но все еще может быть хорошо. Я посмотрю на python, если ни у кого нет ничего более стандартного (хотя, похоже, нет).
VitalyB

2

Вот онлайн-инструмент для сравнения текстов: http://text-compare.com/

Он может выделить каждый отдельный символ, который отличается, и продолжить сравнение остальных.


Похоже, что это делает различия на уровне строки без опции для отдельных символов. Как у вас получается сравнивать персонажей?
Dragon

Ах; он выделяет разные персонажи. Но это все еще линейный уровень в этом catdogи cat\ndogбудет соответствовать толькоcat
Dragon

1

Я думаю, что более простое решение всегда будет хорошим решением. В моем случае мне очень помогает приведенный ниже код. Я надеюсь, что это поможет кому-нибудь еще.

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

Вы можете сравнить два файла со следующим синтаксисом на своем любимом терминале:

$ ./diff.py fileNumber1 fileNumber2

0

Если вы храните свои файлы в Git, вы можете различать версии с помощью скрипта diff-highlight , который будет показывать разные строки с выделенными различиями.

К сожалению, это работает только тогда, когда количество удаленных строк совпадает с количеством добавленных строк - есть код-заглушка, когда строки не совпадают, поэтому, по-видимому, это можно исправить в будущем.


0

Не полный ответ, но если cmp -lвывод недостаточно ясен, вы можете использовать:

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

в OSX используйте `` sed 's / (.) / \ 1 \' $ '\ n / g' file1> file1.vertical sed 's / \ (. \) / \ 1 \' $ '\ n / g 'file2> file2.vertical ``
mmacvicar

0

В большинстве этих ответов упоминается использование модуля Perl diff-highlight . Но я не хотел выяснять, как установить модуль Perl. Поэтому я внес в него несколько незначительных изменений, чтобы он стал автономным скриптом Perl.

Вы можете установить его, используя:

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

И использование (если у вас есть Ubuntu, colordiffупомянутый в ответе zhanxw):

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

И использование (если вы этого не сделаете):

▶ diff -u f1 f2 | DiffHighlight.pl

0

ccdiffудобный специализированный инструмент для решения поставленной задачи. Вот как выглядит ваш пример:

Пример вывода ccdiff

По умолчанию он выделяет различия в цвете, но его можно использовать и на консоли без поддержки цвета.

Пакет включен в основной репозиторий Debian:

ccdiff - это цветной diff, который также окрашивает измененные строки.

Все инструменты командной строки, которые показывают разницу между двумя файлами, не могут показать визуально полезные незначительные изменения. ccdiff пытается придать внешний вид diff --colorили colordiff, но расширяет отображение цветного вывода от цветных удаленных и добавленных строк до цветов для удаленных и добавленных символов в измененных строках.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.