编码与换行

在 Shift_JIS 里,「本」这个字是两个字节 96 7B,而 7B 就是 ASCII 的 {;「表」是 95 5C5C 则是反斜杠。也就是说,字符编码一旦搞错,日文正文会逐字节变成 LaTeX 的控制序列和花括号。这正是 TeX 里的乱码从来不只是"出现看不懂的字"的原因,也是今天的 .tex 应当以 UTF-8 + LF 保存的原因。本页用真实的报错原文,说明遇到 Shift_JIS、EUC-JP、ISO-2022-JP 的遗留文件时会发生什么、该怎么转换,以及 platexuplatex 能处理的字符范围有何不同。

.tex 应该用什么保存:UTF-8 与 LF

UTF-8(有无 BOM 皆可)与 LF。TeX Live 2024 的 uplatexlualatexxelatex 默认都按 UTF-8 读取,Git 生成差异时也以 UTF-8 和 LF 为前提。日文圈之所以在这里格外麻烦,是因为在 Unicode 普及之前,三种互不兼容的编码同时并存:Windows 用 Shift_JIS,Unix 用 EUC-JP,电子邮件为了通过七位信道而用 ISO-2022-JP(所谓"JIS 码")。同一个汉字有三套字节序列,而且光看文件内容也无法确凿分辨。所以打开一个旧实验室目录时,第一个该怀疑的就是编码。

编码使用场景如今还会遇到的地方
UTF-8当前标准(Unicode)新工作唯一选择,也是 TeX Live 的默认
Shift_JIS旧 Windows、DTP、游戏主机早年分发的模板、光盘附录;也叫 CP932
EUC-JP旧 Unix、大学计算中心仍留在实验室共享目录里的 .tex.sty
ISO-2022-JP电子邮件("JIS 码")用转义序列切换字符集的七位方案;从邮件粘贴来的片段

把 Shift_JIS 文件当 UTF-8 打开会发生什么

屏幕不会被看不懂的字填满。报错会接连出现,最后倒在 ! Undefined control sequence. 上。 把 Shift_JIS 文件直接喂给 TeX Live 2024 的 uplatex,先出现 ! 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 语法。于是症状不是"日文显示不对",而是"LaTeX 说某个我根本没写过的命令未定义",这正是编码最不容易被怀疑的原因。

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 的设计所必然带来的。由于双字节字符的第二个字节被允许落在 ASCII 范围 0x400x7E 之内,第二个字节恰好是 0x5C(反斜杠)的字符有 52 个,恰好是 0x7B(左花括号)的有 50 个。前者包括 表、十、ソ、能、貼、暴、申、構;后者包括 本、宮、施、旬、養、鶏——都是日常日文里再普通不过的字。日本开发者把它们叫作"坏字",并与之缠斗多年,因为同样的事故也会发生在 shell 脚本和配置文件里,不只是 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/iconviconv -f CP932 -t UTF-8 old.tex > new.tex 就能完成 Shift_JIS 到 UTF-8 的转换。如果装了 nkfnkf -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 时,编码名请写 CP932 而不是 SHIFT_JIS。两者常被当成一回事,其实不是。让 macOS 的 iconv 把含 (波浪号)或 (带圈数字)的文本转成 SHIFT_JIS,会停在 iconv: iconv(): Illegal byte sequence;换成 CP932 就能通过。SHIFT_JIS 是忠于 JIS X 0208 的窄定义,不包含 NEC 与 IBM 扩展字符——带圈数字、罗马数字之类。来自 Windows 的文件实际上都是 CP932,所以默认写 CP932 才对。需要反向(UTF-8 → Shift_JIS)转给老工具时,理由相同。

nkf 选项作用iconv 对应写法
-g检测当前编码与换行;不做转换没有对应;改用 filechardetect 之类
-w转换为 UTF-8(无 BOM)iconv -t UTF-8
-s / -e / -j转换为 Shift_JIS / EUC-JP / ISO-2022-JPiconv -t CP932 / -t EUC-JP / -t ISO-2022-JP
-Lu / -Lw / -Lm把换行统一为 LF / CRLF / CR没有对应;用 seddos2unix 或 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 的范围内,范围之外的字在门口就被挡下。新文档已经没有理由选 platexuplatex 设为默认,𠮟 以及名册里的各种异体字都能通过。

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)写成的 .tex 交给 uplatex,或者文件开头带着 UTF-8 的 BOM(EF BB BF),在 TeX Live 2024 上 uplatexlualatex 都会毫无警告地编译通过。常听说 BOM 会在文档开头留下一个看不见的字符,至少对今天这两个引擎来说并非如此。受苦的不是 LaTeX,而是周边的工具。CRLF 与 LF 混杂的文件在 Git 里会显示为整篇都被改动,评审无从谈起。行尾残留 \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 开始。CP932 而不是 SHIFT_JIS,否则带圈数字和波浪号会转换失败。
  • 若装有 nkfnkf -w -Lu --overwrite *.tex 最快——但 macOS 和多数 Linux 都不自带。
  • 转换前先复制或提交到 Git。 --overwrite 无法撤销。
  • 新文档请用 uplatex(或 lualatex)。 普通 platex 遇到 JIS X 0208 之外的字(如 髙,U+9AD9)会报错。
  • -kanji=sjis 只是急救。 一旦看清内容,就把文件本身转成 UTF-8。