텍스트 비교
두 텍스트를 나란히 비교하고 줄별로 차이를 강조 표시
텍스트 A
텍스트 B
텍스트 비교(Diff)란?
텍스트 diff(difference의 줄임말)는 두 텍스트 블록을 비교하여 어떤 줄이 추가, 삭제, 또는 변경 없이 유지되었는지 식별한 결과물입니다. 이 개념은 1974년 Version 5 Unix의 일부로 처음 출시된 Unix diff 유틸리티에서 비롯되었습니다. 오늘날 텍스트 diff는 Git과 같은 버전 관리 시스템의 근간으로, 모든 커밋은 각 파일의 전체 복사본이 아닌 diff를 저장합니다.
diff 알고리즘은 두 줄 시퀀스 간의 LCS(Longest Common Subsequence, 최장 공통 부분 수열)를 찾습니다. LCS에 포함된 줄은 변경되지 않음으로 표시됩니다. 원본 텍스트에 있지만 LCS에 없는 줄은 삭제됨으로 표시됩니다. 수정된 텍스트에 있지만 LCS에 없는 줄은 추가됨으로 표시됩니다. 결과는 한 텍스트를 다른 텍스트로 변환하는 데 필요한 최소한의 변경 집합입니다.
diff 출력 형식은 여러 가지입니다. 통합 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 |
줄 비교 작동 방식: LCS 알고리즘
이 도구를 포함한 대부분의 줄 비교 도구는 LCS(Longest Common Subsequence, 최장 공통 부분 수열) 알고리즘을 사용합니다. LCS는 연속적일 필요 없이 같은 상대적 순서로 두 텍스트에 모두 나타나는 가장 큰 줄 집합을 찾습니다. LCS에 없는 줄이 실제 차이점입니다.
표준 LCS 알고리즘은 동적 프로그래밍을 사용하며 O(m × n) 시간으로 실행됩니다. 여기서 m과 n은 두 텍스트의 줄 수입니다. 대용량 파일의 경우 Git에서 사용하는 Myers' diff 알고리즘과 같은 최적화 변형이 이를 O(n + d²)로 줄입니다. 여기서 d는 차이의 수로, 대부분의 줄이 공유될 때 빠르게 동작합니다.
코드 예제
JavaScript, Python, Go 및 커맨드라인에서 줄 단위 텍스트 비교 구현. 각 예제는 통합 형식 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