Runaway argument? 是 LaTeX 错误里唯一一条行号不可信的——甚至常常根本不显示行号。少写一个闭合的 },TeX 就会一路去找它,边找边把正文吞进去,直到再也吞不下才叫出声。它此时报告的不是你写错的地方,而是它走到尽头的地方。作为补偿,TeX 会把吞下去的那段文字原样印出来:就在 Runaway argument? 的下一行,那才是真正的地图。本页讲怎么读这张地图、为什么错误里会出现 ! Paragraph ended before \@sect was complete 这种你从没写过的名字,以及如何把 \newcommand* 当作故意设置的绊线来用。
要读的是 runaway text,不是行号
Runaway argument? 的下一行,正好是从没有闭合的那个 { 之后开始的。 也就是说,把这一行开头的几个词拿去编辑器里搜索,就能找到漏掉的 {。这比盯着行号看更快也更可靠。下面是 \textbf{ 忘记闭合时的真实日志。
Runaway argument?
{an important point that never closes. Another sentence on the next l\ETC.
! File ended while scanning use of \textbf .
<inserted text>
\par
<*> t6.tex这里有两处值得注意。一是结尾的 \ETC.——这是 TeX 的标记,表示吞下的文字太长,按屏幕宽度被截断了。二是最后一行不是 l.6 这样的行号,而是文件名 <*> t6.tex。这种形式意味着 TeX 把输入读完了,也就是一路跑到了文件末尾,那个漏掉的 { 在很靠上的位置。只有当中途有东西把它拦下来时,才会出现行号。
Paragraph ended before ... was complete:短参数里混进了空行
这条消息的意思是:参数里出现了空行。 对 TeX 来说空行不是空白,而是一个名为 \par 的 token,而 \par 不能出现在「短」参数里——也就是定义时没有加 \long 的宏的参数。所以本想换个行、结果在 \section{...} 里夹进了空行,即使 } 写得好好的,也照样 runaway。
Runaway argument?
{A heading that runs on
! Paragraph ended before \@sect was complete.
<to be read again>
\par
l.5 and on across a blank line}你肯定没写过 \@sect 这个名字。事实上 \section 本身是以 \long 定义的,空行是能穿过它的——真正接手工作的内部宏 \@sect 才不是 \long(在 TeX Live 2024 上用 \show 即可当场验证)。消息里出现的不是你敲下的命令,而是当时真正在收集参数的那个内部宏。 在 \caption、\footnote 和目录相关的地方也会冒出类似的陌生名字。看到不认识的名字不必慌,去读 Runaway argument? 那一行就好。
为什么参数里根本不许出现 \par
这条限制不是为了给人添麻烦,而是特意设来抓漏写的闭合括号的。 TeX Live 随附的 Victor Eijkhout《TeX by Topic》在讲到「参数里不许有 \par」这条规则时说,它「对定位被遗忘的闭合括号很有用」,并接着写道:「空行会生成一个 \par,而这在多数情况下意味着有一个闭合括号被忘掉了。」换句话说,短参数是一道让暴走尽早停下的保险。没有这条规则,只要漏掉一个 {,TeX 就会一声不吭地吞掉整篇文档,最后什么线索也不留。
稍作试验就能看出这个机制有多字面。TeX 拒绝的是 名为 \par 的那个 token 本身,而不是「段落结束」这层意思。\endgraf 是与 \par 行为完全相同的别名,可把它写进短参数里却什么也不会发生;在同一个位置直接写 \par,! Paragraph ended before ... was complete 当场就出来了。TeX 只是在比对 token 的名字——正因如此,空行这种本来毫无意义的空白,才成了它能可靠察觉的信号。
与 File ended while scanning use of ... 的区别
Runaway argument? 之后可能跟着两种行,它们告诉你这场暴走跑出了多远。 半路被空行拦下,得到的是 Paragraph ended before ... was complete;一路没被拦住,得到的就是 File ended while scanning use of ...。再往下一行还有更细的线索:<to be read again> \par 表示 TeX 读到了由空行生成的 \par 并把它退了回去;<inserted text> \par 表示输入已经读完,是 TeX 自己插了一个 \par。前者说明原因就在附近,后者说明原因在很远的地方。
| 消息 | 含义 | 日志指向何处 |
|---|---|---|
Runaway argument? | 参数始终没有收完;下一行必定是被吞下的文字 | 那一行的开头就在未闭合的 { 之后 |
Paragraph ended before ... was complete | 短参数里混进了空行(即 \par) | 空行所在的行号;{ 就在稍上方 |
File ended while scanning use of ... | 直到文件末尾都没有出现 } | 不是行号,而是 <*> 文件名;{ 在很靠上的位置 |
把 \newcommand* 当成故意设下的绊线
\newcommand 造出的是 \long 宏,\newcommand* 造出的是短宏。 用 \show 一看便知:前者显示 \long macro:,后者只显示 macro:。这个差别在 runaway 时体现得最清楚。若忘记闭合不带星号定义的宏的 {,TeX 会毫不在意地越过空行一路跑到文件末尾,最后以 ! Emergency stop. 收场,连 PDF 都不生成。带星号的话,它会在第一个空行处停住,报出你那个宏的名字,而且整篇仍然能排完。这不是日志多几行少几行的差别,而是把可疑范围锁定在几十行、还是扩大到整篇文档的差别。
% short: the runaway stops at the first blank line, and the run survives
\newcommand*{\keyword}[1]{\textbf{#1}}
% ! Paragraph ended before \keyword was complete.
% <to be read again>
% \par
% long (the default): the runaway reaches end of file
\newcommand{\keyword}[1]{\textbf{#1}}
% ! File ended while scanning use of \keyword.
% ! Emergency stop.实用的准则很简单:凡是参数不需要装下整段文字的宏,就用 \newcommand* 定义。 标题、术语、单位、短标签——绝大多数自己写的宏都属于这一类。只有本就打算接收段落的宏(比如带框注释的正文)才用不带星号的形式。星号本身的含义以及它与 \renewcommand 的关系,由「定义宏」那一页负责。另外,把 \verb 或 verbatim 环境塞进参数里会以完全不同的方式出问题——原因见「原样输出」那一页。
实际动手找出缺失的 {
先搜索 runaway text 的开头;如果找不到,就去读它的「内容」。 被吞下的文字里常常混着本不该出现在参数中的东西,而那正好圈定了范围。比如 runaway text 里出现了 \begin {document},那么未闭合的 { 就在导言区——忘记闭合 \newcommand 的定义体,得到的正是这个样子。如果看到 \end {document},说明暴走一路跑到了文档末尾。范围大致定下来之后,编辑器的括号匹配功能就派上用场了:把光标放在可疑的 { 上,看它的另一半落在哪里。若还是缩不小范围,就把文档注释掉一半再编译,哪一半还报错就再对半砍——这样重复几轮,必定能落到具体位置。
Runaway argument?
{Hello \begin {document} Body. \end {document}
! File ended while scanning use of \@argdef.