テキスト差分ツール
2つのテキストを並べて比較し、行ごとの差分をハイライト表示
テキスト A
テキスト B
テキスト差分とは?
テキスト差分(diff)とは、2つのテキストブロックを比較し、追加・削除・変更なしの行を特定した結果です。この概念は、1974年にVersion 5 Unixの一部として初めてリリースされたUnixの diff ユーティリティに由来しています。現在、テキスト差分はGitなどのバージョン管理システムの基盤となっており、各コミットはファイルの完全なコピーではなくdiffを保存しています。
diff アルゴリズムは、2つの行シーケンス間の最長共通部分列(LCS)を見つけます。LCSに含まれる行は「変更なし」としてマークされます。元のテキストにあってLCSにない行は「削除」としてマークされます。変更後のテキストにあってLCSにない行は「追加」としてマークされます。結果は、一方のテキストをもう一方に変換するために必要な最小限の変更セットです。
diff の出力にはいくつかの形式があります。unified diff(git diff のデフォルト)は削除行にマイナス記号、追加行にプラス記号を付けます。サイドバイサイド diff は両テキストを並列カラムに配置します。このツールは行ごとの比較とカラーコード出力を使用します:追加は緑、削除は赤、変更なしはニュートラルです。変更なしの行はデフォルトでプレフィックスなしで表示されますが、変更箇所のみに集中するために非表示にできます。
オンラインテキスト差分ツールを使う理由
ターミナルでテキストを比較するには diff ユーティリティのインストールとコマンドラインフラグの操作が必要です。ブラウザベースの diff ツールはその手間を完全に排除します。
テキスト差分のユースケース
diff 出力形式の比較
diff ツールはいくつかの形式で出力を生成します。以下の表は最も一般的な形式、それを生成するツール、各形式が有用な場面をまとめています。
| 形式 | ツール / ソース | 説明 |
|---|---|---|
| Unified diff | diff -u / git diff | Prefixes lines with + / - / space; includes @@ hunk headers |
| Side-by-side | diff -y / sdiff | Two columns, changed lines aligned horizontally |
| Context diff | diff -c | Shows changed lines with surrounding context, marked with ! / + / - |
| HTML diff | Python difflib | Color-coded HTML table with inline change highlights |
| JSON Patch | RFC 6902 | Array of add/remove/replace operations on a JSON document |
行 diff の仕組み:LCSアルゴリズム
このツールを含むほとんどの行 diff ツールは、最長共通部分列(LCS)アルゴリズムを使用します。LCS は、連続している必要はなく、同じ相対順序で両テキストに現れる行の最大セットを見つけます。LCS にない行が実際の差分です。
標準LCSアルゴリズムは動的計画法を使用し、O(m x n) 時間で実行されます(m と n は2つのテキストの行数)。大きなファイルでは、Myersの diff アルゴリズム(Gitが使用)などの最適化バリアントがこれを O(n + d^2) に削減します(d は差分の数)。ほとんどの行が共通の場合に高速です。
コード例
JavaScript、Python、Go、コマンドラインでの行ごとのテキスト比較の実装例。各例は unified-style の diff 出力を生成します。
// Line-by-line diff using the LCS algorithm
function diffLines(a, b) {
const linesA = a.split('\n')
const linesB = b.split('\n')
// Build LCS table
const m = linesA.length, n = linesB.length
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))
for (let i = 1; i <= m; i++)
for (let j = 1; j <= n; j++)
dp[i][j] = linesA[i-1] === linesB[j-1]
? dp[i-1][j-1] + 1
: Math.max(dp[i-1][j], dp[i][j-1])
// Backtrack to produce diff
const result = []
let i = m, j = n
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && linesA[i-1] === linesB[j-1]) {
result.unshift({ type: 'equal', text: linesA[i-1] }); i--; j--
} else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) {
result.unshift({ type: 'add', text: linesB[j-1] }); j--
} else {
result.unshift({ type: 'remove', text: linesA[i-1] }); i--
}
}
return result
}
const diff = diffLines("alpha\nbeta\ngamma", "alpha\nbeta changed\ngamma\ndelta")
// → [
// { type: 'equal', text: 'alpha' },
// { type: 'remove', text: 'beta' },
// { type: 'add', text: 'beta changed' },
// { type: 'equal', text: 'gamma' },
// { type: 'add', text: 'delta' }
// ]import difflib
text_a = """alpha
beta
gamma""".splitlines()
text_b = """alpha
beta changed
gamma
delta""".splitlines()
# Unified diff (same format as git diff)
for line in difflib.unified_diff(text_a, text_b, fromfile='a.txt', tofile='b.txt', lineterm=''):
print(line)
# --- a.txt
# +++ b.txt
# @@ -1,3 +1,4 @@
# alpha
# -beta
# +beta changed
# gamma
# +delta
# HTML side-by-side diff
d = difflib.HtmlDiff()
html = d.make_file(text_a, text_b, fromdesc='Original', todesc='Modified')package main
import (
"fmt"
"strings"
)
// Minimal LCS-based line diff
func diffLines(a, b string) {
la := strings.Split(a, "\n")
lb := strings.Split(b, "\n")
m, n := len(la), len(lb)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if la[i-1] == lb[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else if dp[i-1][j] >= dp[i][j-1] {
dp[i][j] = dp[i-1][j]
} else {
dp[i][j] = dp[i][j-1]
}
}
}
var result []string
i, j := m, n
for i > 0 || j > 0 {
if i > 0 && j > 0 && la[i-1] == lb[j-1] {
result = append([]string{" " + la[i-1]}, result...)
i--; j--
} else if j > 0 && (i == 0 || dp[i][j-1] >= dp[i-1][j]) {
result = append([]string{"+" + lb[j-1]}, result...)
j--
} else {
result = append([]string{"-" + la[i-1]}, result...)
i--
}
}
for _, line := range result {
fmt.Println(line)
}
}
// Output:
// alpha
// -beta
// +beta changed
// gamma
// +delta# Compare two files with unified diff (3 lines of context) diff -u original.txt modified.txt # Git diff between working tree and last commit git diff HEAD -- file.txt # Git diff between two branches git diff main..feature -- src/ # Side-by-side diff in the terminal diff -y --width=120 original.txt modified.txt # Color-coded diff (requires colordiff) diff -u original.txt modified.txt | colordiff