인코딩과 줄바꿈

Shift_JIS에서 「本」이라는 글자는 두 바이트 96 7B이고 뒤의 7B는 ASCII의 {이며, 「表」는 95 5C이고 5C백슬래시입니다. 즉 문자 인코딩을 잘못 잡는 순간, 일본어 본문이 바이트 단위로 LaTeX의 제어열과 중괄호로 둔갑합니다. TeX에서 문자 깨짐이 "읽을 수 없는 글자가 나온다"로 끝나지 않는 이유가 여기 있고, 지금의 .texUTF-8 + LF로 저장해야 하는 이유도 여기 있습니다. 이 페이지에서는 Shift_JIS・EUC-JP・ISO-2022-JP의 잔재를 만났을 때 실제로 무슨 일이 일어나고 어떻게 고치는지, 그리고 platexuplatex이 다룰 수 있는 문자가 어떻게 다른지를 실제 오류 문구와 함께 살펴봅니다.

.tex는 무엇으로 저장해야 하는가 — UTF-8과 LF

UTF-8(BOM 유무는 상관없습니다)과 LF입니다. TeX Live 2024의 uplatex, lualatex, xelatex은 모두 기본값으로 UTF-8을 읽고, Git도 UTF-8과 LF를 전제로 diff를 만듭니다. 일본어권에서 이 대목이 성가신 까닭은 Unicode가 퍼지기 전에 서로 호환되지 않는 인코딩 셋이 나란히 쓰였기 때문입니다. Windows는 Shift_JIS, Unix는 EUC-JP, 메일은 7비트 경로를 통과시키려고 ISO-2022-JP(이른바 「JIS 코드」)를 썼습니다. 같은 한자가 세 가지 바이트열을 갖고, 게다가 파일 내용만 보아서는 확실히 가려낼 수 없습니다. 오래된 연구실 디렉터리를 열었을 때 가장 먼저 의심해야 할 것이 인코딩인 이유입니다.

인코딩쓰이던 곳지금도 마주치는 자리
UTF-8현재 표준(Unicode)새 작업은 이것뿐이며 TeX Live의 기본값이기도 합니다
Shift_JIS구 Windows, DTP, 게임기배포된 옛 템플릿, CD-ROM 부록. CP932라고도 부릅니다
EUC-JP구 Unix, 대학 전산소연구실 공유 디렉터리에 남아 있는 .tex.sty
ISO-2022-JP전자우편(「JIS 코드」)이스케이프 열로 문자 집합을 전환하는 7비트 방식. 메일에서 붙여 넣은 조각

Shift_JIS 파일을 UTF-8인 채로 열면 무슨 일이 일어나는가

화면이 읽을 수 없는 글자로 채워지는 것이 아닙니다. 오류가 잇달아 나오고 마지막에 ! Undefined control sequence.로 넘어집니다. TeX Live 2024의 uplatex에 Shift_JIS 파일을 그대로 물리면 먼저 ! LaTeX Error: Invalid UTF-8 byte "93.이 나오고, 이어서 ! LaTeX Error: Invalid UTF-8 byte sequence (^^ea^^82̕).가 쌓입니다. 여기까지는 "잘못된 바이트"에 대한 정직한 보고라 아직 친절합니다. 문제는 그다음입니다. 되비쳐진 행이 l.3 ^^93^^fa^^96{^^8c^^ea^^82̕\^^8e로 보이는데——「本」의 뒷바이트가 {로, 「表」의 뒷바이트가 \로 바뀌어 그대로 TeX의 문법이 되어 있는 것이 눈에 들어옵니다. 그래서 증상은 "일본어가 깨진다"가 아니라 "쓴 적도 없는 명령이 정의되지 않았다고 한다"가 되고, 원인이 인코딩임을 알아채기 어려운 것입니다.

terminal
$ uplatex sjis.tex
! LaTeX Error: Invalid UTF-8 byte "93.
l.3 ^^93
! LaTeX Error: Invalid UTF-8 byte sequence (^^ea^^82̕).
! Undefined control sequence.
l.3 ^^93^^fa^^96{^^8c^^ea^^82̕\^^8e

$ uplatex -kanji=sjis sjis.tex        # tell the engine what it is reading
Output written on sjis.dvi (1 page, 320 bytes).

이 사고는 Shift_JIS의 설계에서 필연적으로 나옵니다. 2바이트 문자의 뒷바이트가 ASCII 범위(0x400x7E)까지 파고드는 방식이라, 뒷바이트가 정확히 0x5C(백슬래시)인 문자가 52자, 정확히 0x7B(여는 중괄호)인 문자가 50자 있습니다. 앞쪽에는 表, 十, ソ, 能, 貼, 暴, 申, 構가, 뒤쪽에는 本, 宮, 施, 旬, 養, 鶏가 들어갑니다. 모두 평범한 일본어 문장에 흔히 나오는 글자입니다. 일본 개발자들이 이것을 「나쁜 글자」라 부르며 오래 시달린 까닭은, TeX만이 아니라 셸 스크립트에서도 설정 파일에서도 같은 사고가 났기 때문입니다. EUC-JP에는 이 문제가 없어서(뒷바이트가 언제나 0x80 이상) TeX 현장에서는 한동안 EUC-JP가 선호되기도 했습니다.

Shift_JIS를 UTF-8로 변환하기 — iconv와 nkf

일본어권의 정석은 nkf(Network Kanji Filter)이지만, 이것은 따로 설치해야 하는 도구로 macOS에도 대다수 Linux에도 기본으로 들어 있지 않습니다. 먼저 iconv를 써 보세요. POSIX 표준 도구라 macOS에도 Linux에도 /usr/bin/iconv로 반드시 있습니다. iconv -f CP932 -t UTF-8 old.tex > new.tex면 Shift_JIS에서 UTF-8로의 변환이 끝납니다. nkf가 있다면 nkf -w -Lu --overwrite *.tex 한 줄로 여러 파일을 그 자리에서 고쳐 쓸 수 있으니, 디렉터리째 물려받을 때는 설치할 값어치가 있습니다. 어느 쪽을 쓰든 변환 전에 반드시 복사본을 두거나 Git에 넣고 실행하세요. --overwrite는 말 그대로 원본을 없앱니다.

terminal
# iconv -- always present; safest one file at a time
iconv -f CP932  -t UTF-8 old.tex > new.tex     # Shift_JIS -> UTF-8
iconv -f EUC-JP -t UTF-8 old.tex > new.tex     # EUC-JP    -> UTF-8

# whole tree, keeping a backup of every original
for f in *.tex; do cp "$f" "$f.bak"; iconv -f CP932 -t UTF-8 "$f.bak" > "$f"; done

# nkf, if installed: detect first, then convert in place to UTF-8 + LF
nkf -g old.tex
nkf -w -Lu --overwrite *.tex

iconv을 쓸 때는 인코딩 이름을 SHIFT_JIS가 아니라 CP932로 하세요. 둘이 같은 것이라 여기기 쉽지만 실제로는 다릅니다. macOS의 iconv으로 (물결표)나 (동그라미 숫자)이 든 텍스트를 SHIFT_JIS로 변환하려 하면 iconv: iconv(): Illegal byte sequence에서 멈추고, CP932면 통과합니다. SHIFT_JIS는 JIS X 0208에 충실한 좁은 정의라 NEC・IBM 확장 문자를 포함하지 않기 때문입니다. 동그라미 숫자나 로마 숫자가 그렇습니다. Windows에서 온 파일은 사실상 전부 CP932이므로 CP932를 기본으로 지정하는 것이 정답입니다. 반대 방향(UTF-8 → Shift_JIS)으로 옛 도구에 넘겨야 할 때도 같은 이유로 CP932를 고릅니다.

nkf 옵션기능iconv 대응
-g현재 인코딩과 줄바꿈을 판정합니다(변환하지 않음)대응 없음. file이나 chardetect 같은 도구를 씁니다
-wUTF-8(BOM 없음)로 변환합니다iconv -t UTF-8
-s / -e / -jShift_JIS / EUC-JP / ISO-2022-JP로 변환합니다iconv -t CP932 / -t EUC-JP / -t ISO-2022-JP
-Lu / -Lw / -Lm줄바꿈을 LF / CRLF / CR로 맞춥니다대응 없음. sed, dos2unix, Git의 eol=lf를 씁니다
--overwrite지정한 파일을 직접 고쳐 씁니다대응 없음. iconv은 표준 출력에 쓰므로 반드시 다른 파일로 보냅니다

-kanji= — 변환하지 않고 엔진에 읽는 법을 알려 주기

파일을 고치고 싶지 않거나 고칠 권한이 없을 때는 엔진 쪽에 인코딩을 알려 줍니다. (u)platex-kanji=를 받으며 -kanji=sjis, -kanji=euc, -kanji=jis, -kanji=utf8을 지정할 수 있습니다. 실제로 UTF-8로 읽으면 오류를 잔뜩 뱉던 Shift_JIS 파일이 uplatex -kanji=sjis sjis.tex에서는 Output written on sjis.dvi (1 page, 320 bytes).로 통과했습니다. 다만 이것은 응급 처치입니다. 편집기도 Git도 grep도, \input으로 읽어 들이는 다른 파일도 여전히 UTF-8이라 여기고 움직이므로, 섞이는 순간 다른 사고가 납니다. "받은 원고를 일단 한 번 조판해 내용을 확인한다"까지의 수단으로 여기고, 확인이 끝나면 변환해 두는 것이 정답입니다. 참고로 lualatexxelatex에는 -kanji=가 없습니다. 이들은 언제나 UTF-8을 읽습니다.

platex과 uplatex의 차이 — 같은 바이너리, 다른 문자의 세계

차이는 다룰 수 있는 문자의 범위이지 프로그램 자체가 아닙니다. TeX Live 2024에서 platex --versionuplatex --version을 나란히 돌리면 둘 다 e-upTeX 3.141592653-p4.1.1-u1.30-230214-2.6이라고 밝힙니다. 같은 바이너리입니다. 다른 것은 괄호 안뿐으로, platex은 (utf8.euc), uplatex은 (utf8.uptex)입니다. 읽어 들이는 포맷이 달라 내부에서 문자를 담는 방식이 달라집니다. 그 결과는 구체적입니다. 일본인 성씨에 흔한 (U+9AD9)를 써서 platex에 넣으면 ! LaTeX Error: Unicode character ^^e9^^ab^^99 (U+9AD9)에서 멈추고, uplatex은 아무 말 없이 조판합니다. 맨 platex의 내부는 JIS X 0208 범위에 닫혀 있어 거기 없는 문자는 입구에서 튕겨 나가기 때문입니다. 새로 쓰는 문서에서 platex을 고를 이유는 이제 없습니다. uplatex을 기본으로 두면 𠮟도 명부의 이체자도 통과합니다.

terminal
$ platex --version | head -1
e-upTeX 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.euc) (TeX Live 2024)
$ uplatex --version | head -1
e-upTeX 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.uptex) (TeX Live 2024)

$ platex takashima.tex          # the document contains 髙 (U+9AD9)
! LaTeX Error: Unicode character ^^e9^^ab^^99 (U+9AD9)
$ uplatex takashima.tex
Output written on takashima.dvi (1 page, 304 bytes).

줄바꿈 코드와 BOM — LF・CRLF・CR은 사고가 되는가

LaTeX 쪽은 어느 것이든 잠자코 받아들입니다. \r\n(CRLF, Windows)으로 쓰인 .texuplatex에 넘겨도, UTF-8 앞머리에 BOM(EF BB BF)이 붙어 있어도, TeX Live 2024의 uplatexlualatex도 경고 하나 없이 통과시킵니다. BOM이 있으면 문서 첫머리에 보이지 않는 문자가 남는다는 이야기가 흔하지만, 적어도 지금 이 두 엔진에서는 그렇지 않습니다. 곤란해지는 것은 LaTeX이 아니라 주변 도구입니다. CRLF와 LF가 섞인 파일은 Git의 diff에서 전 행이 바뀐 것처럼 보여 리뷰가 불가능해집니다. 행 끝에 \r가 남은 .stygrep하면 행 끝 앵커가 듣지 않을 수 있습니다. 그러므로 LF로 통일하는 것은 조판을 위해서가 아니라 협업을 위해서입니다. Git을 쓴다면 .gitattributes에 한 줄 더하는 것이 가장 확실하고, 체크아웃 시 참여자들의 환경 차이까지 흡수해 줍니다.

terminal
# .gitattributes -- normalise on checkin, hand out LF on checkout
*.tex text eol=lf
*.sty text eol=lf
*.bib text eol=lf
*.pdf binary

# one-off cleanup of a file that arrived with CRLF
sed -i.bak $'s/\r$//' old.tex
  • 새것이든 기존것이든 UTF-8 + LF로 통일합니다. TeX Live 2024 세 엔진 모두의 기본값이며 Git과도 맞물립니다.
  • 변환은 iconv -f CP932 -t UTF-8부터. SHIFT_JIS가 아니라 CP932를 지정합니다(동그라미 숫자와 물결표가 변환되지 않습니다).
  • nkf가 있다면 nkf -w -Lu --overwrite *.tex가 가장 빠릅니다. 다만 macOS에도 대다수 Linux에도 기본으로 들어 있지 않습니다.
  • 변환 전에 복사본이나 Git 커밋을. --overwrite는 되돌릴 수 없습니다.
  • 새 문서는 uplatex(또는 lualatex)으로.platex은 JIS X 0208 밖의 글자, 예컨대 髙(U+9AD9)에서 오류가 납니다.
  • -kanji=sjis는 응급 처치입니다. 내용을 확인했으면 파일 자체를 UTF-8로 변환하세요.