Використання 'diff' (або що-небудь ще) для отримання різниці на рівні символів між текстовими файлами


91

Я хотів би використовувати '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, де для розділення слів не застосовується пробіл.
把 友情 留 在 无 盐

Відповіді:


74

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 розл

Зверніть увагу, як і доповнення, і видалення розпізнаються на рівні символів, тоді як додавання та видалення нових рядків ігноруються.

Можливо, ви також захочете спробувати одне з таких:

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 також доступний на домашній мові для Mac:brew install colordiff
Emil Stenström

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)
Yaron U.

22

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

Я не бачу жодного інструменту командного рядка, який робить це корисно, але, як зазначає Вілл, приклад коду difflib може допомогти.


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

6
+1 за введення мене до vimdiff. Я знайшов кольори за замовчуванням нечитабельними, але вирішив це на stackoverflow.com/questions/2019281/… .
undefined

18

Ви можете використовувати cmpкоманду в Solaris:

cmp

Порівняйте два файли, і якщо вони відрізняються, повідомляє перший байт і номер рядка, де вони відрізняються.


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

7
Він також доступний на Mac OS X.
Ерік Рет,

Символи можуть складатися з декількох байтів, і OP запитав про візуальне порівняння.
Cees Timmerman

1
@CeesTimmerman: cmp дозволяє візуально порівняти прапор -l -b.
Smar

10

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

Нижче наведені два oneliners, які використовуються 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 витік на

Ось що ви можете зробити з наведеним нижче сценарієм та виділенням виділень (що є частиною 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 ??
Hemant Sharma

1
Що це насправді питання gvim :). command | gvim -буде робити те, що ти хочеш.
Att Righ

Для довідкової інформації висвітлення виділеного місця, як видається, включено як частину gitвашого шляху, але не розміщено на ньому. Одна моя машина, в якій живе /usr/share/doc/git/contrib/diff-highlight.
Att Righ

непрацююче посилання. Як встановити висвітлення. Здається, немає в менеджері пакетів.
Тревор Хікі

3

Дифліб Python може це зробити.

Документація містить приклад програми командного рядка для вас.

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


Дякую! Я розгляну це. Я сподівався на щось більш стандартизоване (наприклад, прихований аргумент командного рядка). Але це може все одно добре. Я розгляну python, якщо ніхто не має нічого більш стандартного (хоча, здається, ні).
VitalyB

2

Ось інструмент для порівняння тексту в Інтернеті: http://text-compare.com/

Він може виділити кожен окремий знак, який відрізняється, і продовжує порівнювати решту.


Здається, це робить різниці на рівні рядка без опції для окремих символів. Як отримати так, щоб порівнювати персонажів?
Дракон

Ах; в ньому виділяються різні персонажі. Але це все ще лінійний рівень, 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. Але я не хотів з'ясовувати, як встановити модуль 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.