在 TeX Live 2024 的安装目录里敲一次 ls -l,第一个意外立刻出现:人人最先学会的 LaTeX 编译命令 latex 根本不是一个程序。它是指向 pdftex 的符号链接,而且另有 19 个名字——pdflatex、etex、xmltex、amstex 等等——都指向同一个文件。你敲哪一个命令仍然极其重要,但原因和多数人设想的不同:名字选择的不是另一个程序,而是 另一个格式文件,有时两个命令之间的全部差别,归结起来只是一个整数。本页讲的是你真正会敲进终端的命令——pdflatex、xelatex、lualatex、latex 加 dvipdfmx,以及 CJK 路线的 platex 和 uplatex——还有少数值得记住的选项,以及编译失败时该怎么读控制台输出。
如何编译 .tex 文件,以及编译后留下的文件
一行就够了:敲 pdflatex document.tex 就会得到 document.pdf。扩展名 .tex 可以省略,选项一律放在文件名 之前。不过产出的不只是 PDF。同一目录下还会出现两个文件:document.aux,一本记录交叉引用与目录信息的账本;以及 document.log,一份完整记录,屏幕上滚过的内容它全都有,还不止。两者都是可以删掉的中间产物——但决定下一步会发生什么的,正是前者。
pdflatex document.tex # -> document.pdf, document.aux, document.log
lualatex document # the .tex extension is optional
xelatex -synctex=1 document.tex # options come before the file name正因为有这个 .aux 文件,编译命令通常需要 运行两次。LaTeX 只从头到尾读一遍文档,所以在第一页排目录时,它还不知道第 7 节会落在第几页。第一遍把已经确定的编号写进 .aux,第二遍再读回来填进正文。若涉及参考文献和索引,往返次数还会更多。LaTeX 如何判断这个循环已经收敛,以及 latexmk 如何将其自动化,由“自动构建”那一页负责。本页讲的是在那个循环里被真正调用的命令本身。
latex 与 pdflatex 的区别:一个整数
latex 输出 .dvi,pdflatex 输出 .pdf,但底下运行的二进制是 同一个文件。在 TeX Live 2024 里两个链接都指向 pdftex,而 latex --version 会毫不掩饰地自报 pdfTeX 3.141592653-2.6-1.40.26 (TeX Live 2024)。差别不在程序,而在每个名字所加载的 格式文件。pdflatex.ini 会读入 pdftexconfig.tex,其中设定了 \pdfoutput = 1;latex.ini 读入完全相同的文件,紧接着用 \pdfoutput=0 覆盖它。追根究底,把这两个命令分开的就是这一个整数。
# TeX Live 2024: four commands, three binaries
readlink $(which latex) $(which pdflatex) $(which xelatex) $(which lualatex)
# pdftex
# pdftex
# xetex
# luahbtex
latex --version
# pdfTeX 3.141592653-2.6-1.40.26 (TeX Live 2024)那么,同一个二进制怎么知道该加载哪个格式?它看的是 自己被以什么名字调用。pdfTeX 的帮助文本说得很直白:它去寻找 NAME.fmt,其中 NAME 就是程序的调用名。改掉链接的名字,启动的就是另一个 LaTeX。要覆盖这个推断,可以传 -fmt=NAME 或 -progname=NAME,也可以在源文件第一行写 %&format。正是这个机制,让二十个名字得以挂在 pdftex 这一个可执行文件上。
\pdfoutput 不只是构建格式时的设定,它是运行期依然有效的 pdfTeX 原语。在文件第一行、\documentclass 之前写上 \pdfoutput=0,即便用 pdflatex 运行,拿到的也是 .dvi。不过实际使用中更干净的做法是走命令行:-output-format=dvi 或 -output-format=pdf,pdfTeX 与 LuaTeX 都接受(唯独 XeTeX 没有这个选项,改用 -no-pdf)。LaTeX 自身也会读取这个值来切换图形处理方式:DVI 模式下加载 l3backend-dvips.def,PDF 模式下加载 l3backend-pdftex.def。这正是为什么一条路线接受 .eps 插图,另一条接受 .pdf 和 .png。
pdflatex、xelatex、lualatex 的区别:按字体与 Unicode 来选
判断标准只要一条就够。文档以拉丁字母为主、又想要速度和最大限度的宏包兼容性,就用 pdflatex。一旦你想按名字指定操作系统里已装好的字体,或者要排拉丁字母以外的文字,就该转向 xelatex 或 lualatex。三者都输出 PDF,也都接受同一份 .tex;截然不同的只是字体进入的那道门。
命令为什么会增加到这个数量?历史给出的是一条直线。Knuth 从 1978 年开始写 TeX,它的输出格式是 DVI(device-independent)——因为 PDF 要到 1993 年才由 Adobe 推出,那时根本还不存在。pdfTeX 正是弥合这段距离的扩展。作者 Hàn Thế Thành 的博士研究做的是 微排版:让字符在行末稍稍探出版心之外,并以肉眼不可察觉的幅度伸缩字宽,从而让整页的灰度更均匀。不经 DVI 直接写出 PDF,正是那项研究的产物。今天 pdflatex 最快、也被最多宏包默认假定,理由很简单:它被使用的时间最长。
xelatex 运行的是 Jonathan Kew 约在 2004 年开发的 XeTeX。配合 fontspec 宏包,只要写出系统里 OpenType 字体的名字就能使用。不过 XeTeX 并不直接写 PDF:它先生成 DVI 的扩展形式 .xdv,再交给 xdvipdfmx 转成 PDF。加上 -no-pdf 会停在 .xdv 阶段,而 -output-driver=CMD 可以替换转换程序本身。三者中只有 XeTeX 没有 -output-format 选项,正是这一结构的直接结果。
lualatex 里藏着一个转折。在 TeX Live 2024 中,这个链接指向的不是 luatex,而是 luahbtex——即内置了字形整形库 HarfBuzz 的 LuaTeX 版本,正是这一部分让阿拉伯文、印度系文字等整形规则复杂的书写系统能够正确排出。素的 luatex 依然存在,下文的 dvilualatex 指向的就是它。LuaTeX 的招牌就写在名字里:内嵌的 Lua 解释器,让文档能够伸手进入断行、字体加载等排版内部处理。把版本号排在一起看,会发现一件有意思的事。tex --version 报的是 TeX 3.141592653——Knuth 为 TeX 编号的方式是每次更新就把圆周率再多写一位,pdfTeX 和 XeTeX 都原样继承了这个前缀。唯独 LuaTeX 退出了这一传统,自称 Version 1.18.0。
| 命令 | 实际二进制(TeX Live 2024) | 输出 | 字体与字符 |
|---|---|---|---|
pdflatex | pdftex | TeX 内置字体;最快、兼容面最广 | |
xelatex | xetex | PDF(内部经 .xdv) | 通过 fontspec 使用系统 OpenType 字体 |
lualatex | luahbtex | 系统字体 + HarfBuzz 整形 + Lua 脚本 | |
latex | pdftex | DVI | TeX 内置字体;EPS 插图与 PSTricks 的路线 |
dvilualatex | luatex | DVI | 需要 DVI 输出但想用 LuaTeX 功能时 |
platex | euptex | DVI | 日文;内部编码为 EUC,限于 JIS X 0208 |
uplatex | euptex | DVI | 日文;内部编码为 Unicode,罕用字也能处理 |
latex 接 dvipdfmx:DVI 路线为什么还活着
理由至今仍有两条。其一,有些机制只会说 DVI:以 PostScript 为前提作图的宏包,PSTricks 首当其冲,只有走 latex 到 dvips 这条路才能发挥全部本事。其二,下一节要谈的日文排版,传统上就是走这条路。latex document.tex 生成 document.dvi,dvipdfmx document.dvi 再把它变成 PDF;若需要 PostScript,就改用 dvips。至于“想用 LuaTeX 的功能但仍要 DVI”这种较少见的需求,有 dvilualatex,它指向不含 HarfBuzz 的素 luatex。
latex document.tex # -> document.dvi
dvipdfmx document.dvi # -> document.pdf
dvips document.dvi # -> document.ps (for PSTricks and friends)中日韩的命令:platex、uplatex 及其替代路线
日文有专属的命令,是因为日文排版有专属的规则:竖排、约束哪些字符可以出现在行首行尾的禁则处理,以及和文与西文之间应有的固定间距。pTeX 把这些规则直接嵌进引擎本身而不是宏里,跑在它上面的 LaTeX 就是 pLaTeX,也就是 platex 命令。格式文件 platex.ini 的第一行至今写着 “for pLaTeX (ASCII Nihongo LaTeX)”,留下了它出自 ASCII 的痕迹。upTeX 是田中琢尔对 pTeX 的扩展,把内部字符编码换成完整的 Unicode;跑在其上的 LaTeX 是 upLaTeX,即 uplatex 命令。两者的输出一律是 DVI,从不直接写 PDF。
同样的意外在这里重演。在 TeX Live 2024 中,platex 和 uplatex 都是指向同一个二进制 euptex 的链接,ptex、eptex、uptex 也一并汇入。区分它们的是内部汉字编码。platex --version 显示 e-upTeX 3.141592653-p4.1.1-u1.30-230214-2.6 (utf8.euc),而 uplatex --version 的结尾是 (utf8.uptex)。换句话说,同一个可执行文件按调用名切换 -kanji-internal。pLaTeX 以内部 EUC 的经典 pTeX 方式工作,可处理的汉字大致限于 JIS X 0208;upLaTeX 内部为 Unicode,罕用人名汉字与完整的 CJK 统一汉字都能直接通过。新建日文文档时 uplatex 成为默认选择,靠的就是这一点差别。这次合并是相当晚近的变化,而且有确切日期:随 TeX Live 分发的 pTeX 官方指南记载,platex 从 TeX Live 2012 到 2022 一直运行在 e-pTeX 之上,2023-06-01 起改用 e-upTeX 的 legacy-encoding-compatibility mode。upTeX 的额外原语因此也能在 pLaTeX 中使用,而日文字符的内部编码则被刻意保留为非 Unicode,以维持向后兼容。
内部编码和输入文件的编码是两回事。输入侧用 -kanji=STRING 指定,可取 euc、jis、sjis、utf8、uptex。近年的 TeX Live 默认 UTF-8,所以常常可以省略;但显式写出,换了环境也不会出岔子。要可靠地处理无 BOM 的 UTF-8,可以再加 -no-guess-input-enc,直接关掉编码猜测。输出是 DVI,收尾交给 dvipdfmx。
# Japanese, the traditional route: typeset -> DVI -> PDF
uplatex -kanji=utf8 -no-guess-input-enc document.tex # -> document.dvi
dvipdfmx document.dvi # -> document.pdf中文和韩文根本不走这条路。两者通常都交给 Unicode 原生的引擎:中文用 xelatex 或 lualatex 配 ctex 宏包(其内部会调用 xeCJK 等),韩文则用同样的引擎配 kotex。日文也有同样的选择——在 lualatex 下加载 luatexja,PDF 就能直接产出。走这条路就不再需要 -kanji:引擎从头到尾都是 Unicode,压根没有需要切换的内部编码。
值得敲的选项:-interaction=nonstopmode、-halt-on-error、-output-directory
选项一律放在文件名之前,下列各项在 pdfTeX、XeTeX、LuaTeX 的任一命令上都能用。日常真正管用的其实是四个:-synctex=1 用于编辑器联动,-interaction=nonstopmode 让编译不中途卡住,-halt-on-error 则相反,在第一个问题处就收手,还有 -file-line-error,把消息变成机器能解析的形式。
| 选项 | 作用 |
|---|---|
-synctex=1 | 写出 document.synctex.gz,使编辑器与 PDF 之间可以互相跳转 |
-interaction=nonstopmode | 遇错不等待输入,一直跑到结束;batchmode 还会抑制终端输出 |
-halt-on-error | 在第一个错误处放弃;不会生成 PDF |
-file-line-error | 把消息开头改写成 ./document.tex:3: 形式,便于 IDE 与 CI 解析 |
-output-directory=DIR | 把输出与辅助文件写入 DIR;DIR 必须事先存在 |
-jobname=NAME | 把所有输出文件扩展名之前的部分设为 NAME |
-draftmode | 运行但不写出 PDF(pdfTeX / LuaTeX);适合只为确定引用的中间遍 |
-output-format=FORMAT | 选择 dvi 或 pdf(仅 pdfTeX 与 LuaTeX;XeTeX 用 -no-pdf) |
-shell-escape | 解除对通过 \write18 运行外部命令的一切限制(请读下一节的警告) |
pdflatex -synctex=1 -interaction=nonstopmode -halt-on-error -file-line-error document.tex
mkdir -p build # -output-directory will NOT create it for you
pdflatex -output-directory=build document.tex-output-directory 藏着一个 CI 流水线常踩的坑:你指定的目录必须事先存在。 pdfTeX 自己的帮助文本就这么写:它使用已存在的 DIR。目录不存在时,运行会先说 “Please type another transcript file name”,接着以 ! Emergency stop 和 “Fatal error occurred, no output PDF file produced!” 收场。由于原因和排版毫无关系,就算读惯了 LaTeX 日志也会愣一下。解决办法不过是在前一行加一句 mkdir -p。
-shell-escape:minted 为何需要它,它又为何危险
这个选项给了文档 在你的机器上运行任意 shell 命令的权利。不过默认状态下外部命令并非完全封死。TeX Live 运行在 受限模式 下,每次编译都会打印 restricted \write18 enabled. 这一行。在这种状态下,\write18 只能调用 texmf.cnf 白名单上的程序,而 TeX Live 2024 的这份名单很短:bibtex、bibtex8、extractbb、gregorio、kpsewhich、makeindex、memoize-extract.pl、memoize-extract.py、repstopdf、r-mpost、texosquery-jre8。参考文献和索引不加任何选项就能工作,正是因为这两项一开始就被放行了。
那份名单上没有 pygmentize。texmf.cnf 是有意把它排除在外的,并附了一条注释,质疑其过滤功能是否安全。给代码上色的 minted 调用的正是这个程序,因此它在受限模式下无法工作,只能完全打开 -shell-escape。而“完全”就是字面意思:别人给你的 .tex,在那一次编译中就能删除文件、把本地数据发出去,或者装上点什么。判断标准可以很简单——只在编排自己写的、放在自己掌控目录里的文档时才启用。 下载来的模板、评审时转来的投稿,一律不加。
# minted calls pygmentize, which the restricted allow-list does not include
pdflatex -shell-escape document.tex
# turn it off explicitly when compiling a file you did not write
pdflatex -no-shell-escape untrusted.tex编译失败时怎么读控制台输出
要从 第一个以 ! 开头的行 读起,而不是从末尾。LaTeX 的错误会连锁,屏幕最后留下的通常是第一个错误引发的次生伤害,原因在更上面。错误报告的形状总是一样:! 那一行说明症状,下面以 l. 开头的行指出位置。
! Missing $ inserted.
<inserted text>
$
l.3 Some text with a bare x^
2 here.
?关键在 l.3 那一行。它表示源文件第 3 行,但它是 在 TeX 读到的位置恰好折成两段 显示的。折断处之前已被消耗,之后尚未读入。本例中折断点正好落在 x^ 之后,于是上标符号一眼就现了原形。! Undefined control sequence. 也是同样的形状,紧靠折断处之前那个拼错的命令就是答案。末尾的 ? 是输入提示符:编译停在默认的 errorstopmode 上等待输入。按回车继续,输入 x 则中止。
这种交互恰恰是脚本和 CI 最不需要的,交互模式选项就是为此而设。-interaction=nonstopmode 不停顿地把一切都打印出来,batchmode 还会连终端输出一起抑制,scrollmode 则只在找不到文件时才发问。反过来,若只想看第一个问题,-halt-on-error 会让编译以 ! Emergency stop. 和 “Fatal error occurred, no output PDF file produced!” 结束。再加上 -file-line-error,标题会改写成 ./document.tex:3: Missing $ inserted.,编辑器和 CI 就能把它变成可点击的链接。无论选哪一种,完整记录始终会落在 .log 里——终端上错过了,打开那个文件就能读到同样的内容。
到底该敲哪一个编译命令
- 以拉丁字母为主的文档 —
pdflatex。最快,也是多数宏包默认假定的选择。 - 想按名字使用系统字体 —
xelatex或lualatex,两者都通过fontspec指定。 - 整形规则复杂的文字,或想直接脚本化排版过程 —
lualatex(其实体是含 HarfBuzz 的luahbtex)。 - 新建的日文文档 —
uplatex接dvipdfmx,或lualatex配luatexja。 - 中文或韩文 —
xelatex或lualatex配ctex或kotex,完全不经过 DVI。 - PSTricks 等只认 DVI 的机制 —
latex接dvips或dvipdfmx。
最后再给一个排查习惯。编辑器的构建按钮失败时,先在终端手敲同一条命令。如果那样能通过,问题就在编辑器配置,而不在文档。交给别人或放进 CI 之前,用 -halt-on-error -file-line-error -interaction=nonstopmode 跑一遍,让第一个真正的错误位置清晰可读。而日常之中,几乎没有人会手敲这些命令两遍——这活儿交给 latexmk,它会数清遍数并替你调用 dvipdfmx。它凭什么判断该停下来,是“自动构建”那一页的题目。