这里是关于 LaTeX 中脆弱命令的讨论。它说任何具有可选参数的命令都是脆弱的,例如\footnote[2]{myfoottext}
和\footnot{myotherfoottext}
。我试过下面的例子,其中命令\b
有一个可选参数,但它没有 也能工作\protect
。
\documentclass{article}
\def\b[#1]#2{.#2.\bf #1}
\begin{document}
\tableofcontents
\section{\protect\b[one]two} %works
\section{\b[one]two} %also works
\end{document}
以下是文件中的代码.toc
:
\contentsline {section}{\numberline {1}\b [one]two}{1}%
\contentsline {section}{\numberline {2}.t.\bf onewo}{1}%
但是,它显示了该命令的效果\protect
。
答案1
您定义的命令不接受可选参数,它接受分隔参数。如果你这样做:
\def\b[#1]#2{.#2.\bf #1}
\b[one]two
它可以正常工作,但是如果你删除[one]
TeX 将会引发错误:
\def\b[#1]#2{.#2.\bf #1}
\b two
! Use of \b doesn't match its definition.
l.5 \b t
wo
?
因为当您使用 定义命令时\def\b[#1]#2{.#2.\bf #1}
,TeX 期望当您使用 时\b
,输入与参数文本完全匹配(IE, [#1]#2
),这意味着下一个 token必须是[
,如果不是,则会引发错误。请参阅这里对此进行简要描述。
使用时,\def
所有参数都不是可选的!但是,假设您定义:
\newcommand\b[2][--empty--]{.#2.\bf #1}
那么该命令将带有2
参数,其中第一个参数是可选的,如果没有给出默认值为--empty--
。当您使用时\b
,定义的命令实际上不接受任何参数,但它会检查下一个字符是否为[
。如果是,该命令将继续使用“内部” \b
(我们称之为\b@opt
),其定义与您对 一样,使用\def\b@opt[#1]#2{.#2.\bf #1}
。但是,如果您使用\b
而不使用以下[
,则使用 a \b@noopt
,其定义为\def\b@noopt{\b@opt[--empty--]}
。所以最终你还是使用了\b@opt
,但是如果您不给它提供可选参数,底层定义会提供它。
您可以使用以下方式手动定义:
\makeatletter
\def\b{%
\@ifnextchar[%
{\b@opt}{\b@noopt}%
}
\def\b@noopt{\b@opt[--empty--]}
\def\b@opt[#1]#2{.#2.\bf #1}
\makeatother
那么,是什么让可选参数变得“脆弱”呢?
如果命令无法在仅扩展上下文中正常工作,则该命令很脆弱,这通常是在写入临时文件时发生的,例如您所展示的部分标题、标题等,但也发生在内部,或者\edef
最近的\expanded
。
据说带有可选参数的命令很脆弱,因为检查是否存在可选参数的机制(确切地说,\@ifnextchar
上面的宏)通常很脆弱。在某些限制下,可以扩展地检查可选参数,例如在 中xparse
,\NewExpandableDocumentCommand
但通常情况并非如此。
以上面定义的命令为例,如果你执行\edef\test{\b[one]{two}}
(或\write
或\expanded
),TeX 开始从左到右扩展,因此它看到的第一件事是\b
,它会扩展为
\@ifnextchar[{\b@opt}{\b@noopt}
接下来\@ifnextchar
测试扩展为:
\let\reserved@d=[%
\def\reserved@a{\b@opt}%
\def\reserved@b{\b@noopt}%
\futurelet\@let@token\@ifnch
问题就在这里。\let
、\def
和\futurelet
不可扩展,因此 TeX 保留它们的原样,并继续扩展其余部分。 TeX 将扩展那里的所有其他宏,但这样做的话,\let
和\def
将不会定义\reserved@d
和 等,而是扩展它们,这将使代码无法按预期工作。
当然,这只是一个例子,但脆弱性的基本原理是在仅扩展上下文中使用包含不可扩展标记的命令。
如何让命令更加健壮?
直到几十年前,使命令变得健壮的唯一方法是使用 来阻止其扩展\noexpand\command
,这使得 TeX 暂时将其视为\command
不可扩展并在仅扩展的上下文中跳过它。 这样做的缺点是,一旦执行扩展, 就会\noexpand
消失,命令将再次变得脆弱。
为了避免这种情况,LaTeX 定义\protect
和附带的宏\protected@edef
和\protected@write
,它们定义\protect
为\def\protect{\noexpand\protect\noexpand}
。然后,在仅扩展的上下文中将\protect\command
扩展为\noexpand\protect\noexpand\command
。TeX 将丢弃两个\noexpand
,暂时使两者无法扩展。如果您碰巧再次使用该命令,如果您使用宏而不是常规宏,\protect\command
它将继续保持健壮性。\protected@...
使用 LaTeX2ε\newcommand
等定义的带有可选参数的命令具有不同的外观(但底层机制相同)。如果您定义\newcommand\b[2][--empty--]{.#2.\bf #1}
,则\b
实际上将是\protected@testopt \b \\b {--empty--}
(即\\b
命令\\b
,带有两个反斜杠,而不是\\
then b
)。\protected@testopt
将使用\protect
机制来测试它是否可以安全扩展。如果不能,它将离开\protect\b
,否则它将继续使用\\b
,其中包含命令的实际定义。
当 ε-TeX 引入原语时,这一切都变得更容易了\protected
,它允许你制作一个宏引擎保护。这意味着您无需欺骗 TeX 来使用\noexpand
您的宏,而是使用以下方法将宏定义为强大的:
\protected\def\b{%
\@ifnextchar[%
{\b@opt}{\b@noopt}%
}
然后 TeX 本身就会知道\b
不应该在\edef
或\write
或内部扩展\expanded
,无需额外的机制。
\protected
由于向后兼容,LaTeX2ε 不用于定义健壮宏。LaTeX2ε 早于 ε-TeX,因此保护机制建立得更早。例如,LaTeX3 放弃了 2ε 保护机制,仅用于\protected
定义健壮宏。
顺便说一句,我会将您的定义更改为:
\newcommand\mybold[2][--empty--]{.#2.\textbf{#1}}
并用作:
\mybold[one]{two}
我将命令改为\mybold
,因为单字母命令名通常不是一个好主意。我还将\bf
(几十年来已被弃用)改为\textbf
并将第二个参数放在括号中,这样第二个参数就是two
,而不仅仅是t
。
答案2
该页面上的信息是错误的(或至少是过时的,latex2.09 中所有带有可选参数的命令都很脆弱,但 latex2e 自 1993 年起就已经可用......)
问题中的示例没有定义可选参数,但是如果您对其进行更改,使用\newcommand
定义此类参数的功能,您将看到生成的命令非常强大并且可以毫无错误地运行
\documentclass{article}
\newcommand\zb[2][?]{.#2. \textbf{#1}}
\begin{document}
\tableofcontents
\section{\zb[one]{two}} %works
zzz
\section{\zb{three}} %also works
zzz
\end{document}
如果你看一下这个.toc
文件,你会发现它并没有像脆弱的命令那样“爆炸”,它产生了
\contentsline {section}{\numberline {1}\zb [one]{two}}{1}%
\contentsline {section}{\numberline {2}\zb {three}}{1}%
Latex\zb
在这里定义的方式是该\protect
机制在内部使用,因此您不需要明确使用\protect
,所以这种命令根据定义是强大的。
该页面的列表中
所有具有可选参数的命令都是脆弱的。
如上所述,任何带有由\newcommand
(以及其他一些)定义的可选参数的命令都是健壮的(在 LaTeX2e 中一直如此)
所界定的环境
\begin ... \end
十分脆弱。
是的(我们也许有一天会解决这个问题)
显示数学环境,以
\[ ... \]
不,\[
自 2015 年发布以来一直很强大。
数学环境
\( ... \)
然而$ ... $
是强大的
不,\(
自 2015 年发布以来一直很强大。
换行符,
\\
不,\\
自 1994 年发布以来一直很强大
\item
命令
是的。
\footnote
命令
是的。