Missing $ inserted

! Missing $ inserted 是 LaTeX 中最常见的错误,紧随其后的帮助文字以一句 Proceed, with fingers crossed(交叉手指,祈祷着继续)结尾。这段话出自高德纳之手,1980 年代就写进了 TeX,如今每天仍会出现在某个人的屏幕上。它的含义只有一个:只能在数学模式里使用的东西,出现在了正文中。元凶通常是你本想照字面输出的下划线 _、上标 ^、混进正文的 \alpha,或者更早的某处忘记闭合的 $。本页讲解如何读懂这条消息、为什么它报告的行号常常晚于真正的错误位置,以及在正文、\verb\url 这三个答案各不相同的场合里该怎么写。

! Missing $ inserted 到底在抱怨什么

它的意思是:TeX 正在排普通的横向文本时,遇到了只在数学模式中存在的记号——仅此而已。TeX 猜你忘了开数学模式,于是悄悄补上一个 $ 继续排。所以修法一句话就能说完:要当公式就用 $x_1$ 括起来;要的是字符本身就转义成 \_。再往下一层,机制是类别码(catcode)。TeX 给每个输入字符分配一个编号:_ 是 catcode 8(下标),^ 是 7(上标),$ 是 3(数学切换)。而 catcode 7 和 8 只在数学模式里合法。\alpha\frac 报错也是同一个道理——它们内部要求数学类字符。

latex
% "$" is not sacred: it is just the character whose catcode is 3.
% Give catcode 3 to "!" and it opens and closes math exactly like $.
\catcode`\!=3
Then !x_1! typesets as math.   % compiles with zero errors

上面这段代码看着像玩笑,但在 TeX Live 2024 下确实一个错误都不报。$ 并不神圣,它只是恰好 catcode 为 3 的那个字符。想通这一点,这个错误就不再神秘了:它不过是「字符扮演什么角色」的错位。由此也可以推出,在正文里出问题的特殊字符不止 _^——但字符不同,报的错也不同,所以请用下表对号入座。这里有个常见误解:在正文中写 &不会产生 ! Missing $ inserted

字符catcode写在正文里会发生什么
_8! Missing $ inserted。想要字面下划线请写 \_
^7! Missing $ inserted。字面符号是 \textasciicircum
$3开闭数学模式。只写一个会让公式一直开到段落末尾
&4另一种错误:! Misplaced alignment tab character &。字面符号是 \&
%14完全不报错。它吞掉本行剩余内容,包括收尾的 $
#6! You can’t use ‘macro parameter character #’ in horizontal mode
~13不报错;它是不可断行的空格(字面符号:\textasciitilde

如何读懂 <inserted text>l.3

<inserted text> 下面那个 $TeX 自己补上的,并不是你写的字符。而 l.3 那一行才是案发现场:TeX 会把出问题的行折成两段显示,折点正是它读到的位置。下面是把正文只有 The value x_1 is here. 的四行文件交给 pdfLaTeX(TeX Live 2024)后的真实输出。注意折点恰好落在 x_ 之后——TeX 读到下划线的一瞬间就停住了。这种两段式显示是 TeX 错误报告中最有用的部分,因为它能在长行里精确定位。

log
! Missing $ inserted.
<inserted text>
                $
l.3 The value x_
                1 is here.
I've inserted a begin-math/end-math symbol since I think
you left one out. Proceed, with fingers crossed.

同样的消息在 plain TeX 中也会一字不差地出现。因此它并不是 LaTeX 的消息,而是 TeX 引擎自身的消息,即使文件里一行 \usepackage 都没有也照样会出。所以查原因时,应当先怀疑自己敲下的字符,再去怀疑宏包。顺带一提,帮助文字最后那句 Proceed, with fingers crossed,自 1990 年 TeX 冻结以来基本未变。

为什么报告的行号会晚于真正出错的行

TeX 报告的是「它察觉的行」,而不是「你写错的行」。_ 这种一读到就非法的东西,两者是一致的;但对于让公式一直「开着」的错误——比如没有闭合的 $——TeX 要等到段落结束、也就是下一个空行时才会察觉。由于公式不能跨段落,抵达空行才是 TeX 能够断定「数学模式仍开着」的第一时刻。下面这个文件的错误在第 3 行,但错误报告的是第 4 行,也就是那个空行。

document.tex
\documentclass{article}
\begin{document}
The rate is $x% of the total$ per year.

Next paragraph.
\end{document}
log
! Missing $ inserted.
<inserted text>
                $
l.4

I've inserted a begin-math/end-math symbol since I think
you left one out. Proceed, with fingers crossed.

陷阱在第 3 行的 %。未转义的百分号把该行剩下的部分——包括收尾的 $——全部注释掉了,于是数学模式一直开到下一行。l.4 右侧什么都没有,是因为第 4 行本身就是空行。当报错行是空行时,请读作「更早的某处数学模式一直没关」。实务上最快的做法,是回到报错行紧邻的上一段,数一数其中 $ 的个数。编辑器会匹配花括号,却不会匹配 $,这一步只能靠自己。同样的机制下,把 50\% 写成 50% 的那一行,会在几行之后制造出一个莫名其妙的错误。

输出字面下划线:\_\textunderscore\verb 里的 _

如果你只是想输出 data_set.csv 这样的文件名,答案就是 data\_set.csv。但 \_\textunderscore 的关系值得了解。查内核文件 latex.ltx 会看到 \_ 的定义是「在数学模式下用 \nfss@text{\textunderscore},否则用 \textunderscore」——两者其实是同一个东西。也就是说在正文里 \_\textunderscore 完全等价,\_ 只是一个在数学模式下也能用的简写。更重要的在后面:在默认的 OT1 编码下,下划线是作为一条线画出来的,而不是排出来的字符。内核定义为 \leavevmode \kern.06em\vbox{\hrule\@width.3em},即一条宽 0.3em 的横线。结果是你无法从 PDF 里复制出这个下划线,PDF 内搜索也找不到它。加上 \usepackage[T1]{fontenc} 后,t1enc.def 中的 \DeclareTextSymbol{\textunderscore}{T1}{95} 生效,它才成为真正的字形,可复制也可搜索。把同一份文档交给 pdftotext,OT1 版本得到 data set,T1 版本得到 data_set

写法结果适用场合
\__标准写法;正文与公式中都能用
\textunderscore_\_ 展开后的实体;正文中二者无差别
\verb|a_b|a_b无需转义;里面写 \_ 会连反斜杠一起打出来
underscore_宏包;加载后正文中的裸 _ 就安全了
\textasciicircum^字面的 ^,不是上标
latex
% math, when you mean math
The value $x_1$ and $\alpha$ are positive, and $x^2 \ge 0$.

% characters, when you mean characters
Open data\_set.csv, then press \textasciicircum{}C to stop.

% verbatim, when the string is code
Run \verb|make data_set^2| in the shell.

\urlsiunitx 内部,答案是反的

\url{...} 内部不要转义 _ \url{https://example.com/a_b} 原样就能正确通过,反过来写 \url{a\_b} 会连反斜杠一起打印出 a\_b。原因是 url 宏包在读取参数之前会改写类别码,把 _ 及其同伴还原成普通字符。siunitx 也是同样的道理:\num\qty 在内部自行开启数学模式,所以 \qty{2.5}{\micro\metre} 可以直接写在正文里。换句话说,该写什么,取决于你身处哪个命令的参数之中。判断的经验法则很简单:如果该命令按 verbatim 处理内容,就用裸字符;否则就转义。\verb\urllistings\lstinline 属于前者,\section\caption\footnote 属于后者。

latex
\usepackage{url}       % or hyperref, which loads url
\usepackage{siunitx}
...
\url{https://example.com/a_b}    % right: bare underscore
\url{https://example.com/a\_b}   % wrong: prints the backslash
\qty{2.5}{\micro\metre}          % siunitx opens math for you

\[ … \] 中的空行与 ! Display math should end with $$

在陈列公式中放一个空行,会一口气产生三条错误。 你只是想把公式分成两行看得清楚些,文档就坏了。在 \[ E = mc^2F = ma 之间放一个空行,TeX Live 2024 会依次吐出 ! Missing $ inserted! Display math should end with $$! LaTeX Error: Bad math environment delimiter。这三条全都源自同一条规则:公式不能跨越段落边界。要拆分公式请用 align 环境或 \\,绝不要用空行;想在陈列公式中间插入文字,请用 amsmath\intertext。另外,当开闭的美元符号个数不匹配时(例如 $$ … $),也会出现 ! Display math should end with $$。在 LaTeX 中惯用 \[ … \] 而非 $$,因为 $$ 会忽略 \abovedisplayskip 等陈列间距参数。

log
! Missing $ inserted.
<inserted text>
                $
l.5

! Display math should end with $$.
<to be read again>
                   \tex_par:D
l.5

! LaTeX Error: Bad math environment delimiter.
l.7 \]

当一个错误变成五条报错

报错成堆时,只修第一条。 其余几乎都是 TeX 补入的那个 $ 带来的连带损伤。把正文只有 \frac{1}{2} 的文件编译一下,就会得到一串三条:! Missing $ inserted(读到 \frac 处)、! Extra }, or forgotten $.(来自 \frac 定义中 \over 的收尾花括号),以及在 \end{document} 处再来一次 ! Missing $ inserted。第一条错误之后,TeX 仍处在数学模式继续往下跑,于是后面的正文全被当成公式解释——单词中间出现奇怪的空隙,标点间距崩坏,更多错误接踵而至。所以做法是固定的:修好第一条错误,然后重新编译。在那之前去读第二条及以后的消息,纯属浪费时间。

  • 跳到消息给出的 l.N,把该行读到折点为止;折点紧后的那个字符就是元凶。
  • 若折点落在空行上,就去数它前面那一段$ 的个数——奇数说明有一个没闭合。
  • 把同一行读到底,看看是不是有个并非当作注释的 % 吞掉了收尾的 $
  • 要当公式就写 $ … $;要当字符就写 \_\%\&\#\textasciicircum{}
  • 想原样展示代码或标识符,请用 \verblistings,不必再逐个字符斟酌转义。
  • 改完之后先重新编译,再去读下一条错误。