所以我读过几次关于这个、关于“移动参数”和所有这些东西的文章。但这到底是什么意思?有人能用几句话解释一下 TeX 级别发生了什么,脆弱命令和强健命令之间有什么区别吗?此外,我们什么时候需要使用\protect
以及为什么?
答案1
这里的关键概念是,当 TeX 处理输入时,它会做两件不同的事情,分别称为扩展和执行东西。通常,这些活动是交错的:TeX 接受一个标记(即,输入的基本部分),扩展它,然后执行它(如果可能)。然后它对下一个标记执行同样的操作。但在某些情况下,最明显的是,在写入文件时,TeX 只会扩展内容而不执行它们(当 TeX 读回文件时,结果很可能会被(重新扩展并)执行)。某些宏的正常运行依赖于某些内容的正确执行前下一个标记被扩展。这些标记被称为“脆弱的”,因为它们只能在正常(交错)模式下工作,而不能在仅扩展的上下文中工作(例如“移动参数”,这通常意味着写入文件)。
这就是总体情况。现在让我们给出“一些”更多细节。请随意跳到“实践中该怎么做” :)
扩张与执行
之间的区别扩张和执行有点武断,但根据经验法则:
- 扩张变化仅有的输入流,即“TeX 接下来要读取什么”;
- 执行力就是一切。
例如,宏是可扩展的(TeX 接下来将读取它们的替换文本),\input
可扩展的(TeX 接下来将读取给定的文件)等等,\def
不可扩展的(它改变了定义的宏的含义),\kern
不可扩展的(它改变了当前段落或页面的内容)等等。
事情如何出错
现在,考虑一个宏\foo
:
\newcommand\foo[1]{\def\arg{#1}\ifx\arg\empty T\else F\fi}
在正常上下文中,\foo{}
给出T
并foo{stuff}
给出F
。)在正常上下文中,TeX 将尝试扩展\def
(不执行任何操作)然后执行它(\arg{#1}
从输入流中删除并定义\arg
),然后扩展下一个标记\ifx
(删除并可能删除直到输入流中的\arg\empty
匹配项的所有内容,但不包括匹配项),等等。\else
在仅扩展上下文中,TeX 会尝试扩展\def
(不执行任何操作),然后扩展接下来的内容,即\arg
。此时,任何事情都可能发生。也许\arg
未定义,因此您会收到一条(令人困惑的)错误消息。也许它被定义为 之类的东西abc
,因此\foo{}
将扩展为\def abc{} F
。将其写入文件时您不会收到错误,但读回时会崩溃。也许\arg
定义为\abc
,然后\foo{}
会扩展为\def\abc{} F
。然后,无论是写入还是读回,您都不会收到错误消息,但是您不仅会得到F
您所期望的T
,而且还会\abc
得到 被重新定义,如果这是一个重要的宏,则这可能会产生各种后果(祝您好运能够追踪到这个错误)。
保护如何发挥作用
编辑以添加(不是原始问题,但有人在评论中问到):那么如何工作\protect
?好吧,在正常情况下,\protect
扩展为\relax
不执行任何操作。当 LaTeX(不是 TeX)命令即将在仅扩展模式下处理其参数之一时,它会更改\protect
为基于的含义\noexpand
,这避免了下一个标记的扩展,从而保护它免于被扩展但不执行。(有关完整详细信息,请参阅 source2e.pdf 中的 11.4。)
例如,使用\foo
如上所述的方法,如果您尝试,\section{\foo{}}
则会出现如上所述的混乱。现在,如果您这样做,\section{\protect\foo{}}
那么当 LaTeX 打印章节标题时,它处于正常(交错)模式,\protect
扩展为\relax
,然后\foo{}
正常扩展和执行,并且您会在文档中看到一个大 T。在 LaTeX 将章节标题写入.aux
目录文件之前,它会更改\protect
为\noexpand\protect\noexpand
,因此\protect\foo
会扩展为\noexpand\protect\noexpand\foo
并\protect\foo
写入辅助文件。当将辅助文件的该行移动到 toc 文件时,LaTeX 定义\protect
为\noexpand
,因此只会\foo
写入 toc 文件。当最终以正常模式读取 toc 文件时,才\foo
进行扩展和执行,并且您会再次在文档中看到一个 T。
你可以使用下面的文档,查看不带 和带有 的.aux
和文件的内容。注意:(1)你需要手动运行文件,而不是或你的 IDE,后者可能一次运行多次,(2)在尝试非 ed 版本后,你将需要删除 toc 文件进行恢复。.toc
\protect
pdflatex
latexmk
\protect
\documentclass{article}
\newcommand\foo[1]{\def\arg{#1}\ifx\arg\empty T\else F\fi}
\begin{document}
\tableofcontents
\section{\foo{}} % first run writes garbage to the aux file, second crashes
%\section{\protect\foo{}} % this is fine
\end{document}
有趣的事实:如果我们在 的定义中用替换 的每个出现,\arg
则不受保护的版本将以不同的方式失败(如上所述)。\lol
\foo
哪些宏是脆弱的
这是问题中比较简单(但定义明确)的部分。现在,困难的部分是:何时使用\protect
?这要视情况而定。如果不查看其实现,您就无法知道宏是否脆弱。例如,\foo
上面的宏可以使用可扩展技巧来测试是否为空,并且不会脆弱。此外,一些宏是“自发的”(例如,\protect
用 定义的宏)。正如 Joseph 提到的,除非您(或另一个包)加载,否则它是脆弱的。(根据经验,大多数数学模式宏都是脆弱的。)此外,您无法知道特定宏是否尝试仅扩展其参数,但您至少可以确保所有移动参数在某个时候都会仅扩展。\DeclareRobustCommand
\(
fixltx2e
实践中该怎么做
因此,我的建议是:当您看到移动参数中或附近发生奇怪的错误时(即移动到文档另一部分的一段文本,如脚注(移动到页面底部)、章节标题(移动到目录)等),请尝试\protect
执行其中的每个宏。这可以解决 99% 的问题。
(当你将这个技巧应用到同事的文章上时,你就会成为英雄,因为今天要交的文章却“神秘地”崩溃了:看他们的文档几秒钟,然后你会看到\section
标题里有一个数学公式,说“\protect
在这里添加一个”,然后回去工作,让他们称你为巫师。这是一个廉价的伎俩,但很有效。)
答案2
这里的关键概念是扩展。我将以一个假设的“脆弱”函数为例\foo
,用于以下论证\section
:
\section{Some text \foo[option]{argument}}
当 LaTeX 处理\section
宏时,它会执行许多操作。其中之一就是将节名称的文本写入文件.aux
。现在,这里的关键点是它\write
有效地使用了原语:
\immediate\write\@auxout{Some text \foo[option]{argument}}
原\write
语以与 相同的方式扩展其参数\edef
。但是,我说过 是\foo
“脆弱的”。这意味着尝试\edef
这样做要么会导致错误,要么会导致错误的结果。一个典型的例子是任何带有可选参数的宏:这些宏的检测不能在 内扩展\edef
。另一种情况是,某些东西将根据其在输入中的位置进行编号,这可能会导致输出中的编号错误。例如,参见http://texblog.net/help/latex/fragile.html有关脆弱宏的更多详细信息(但请注意fixltx2e
包对其中的一些进行排序)。
当您使用 时\protect
,它会阻止 TeX 在 期间扩展下一个标记\write
。因此文本将“按给定”写入文件.aux
。这当然要求您知道哪些函数需要保护。正如 TH 所指出的,它还需要正确使用\protected@write
或\protected@edef
才能正常工作。(这些宏的工作方式是通过改变 的定义来\protect
实现所需的效果。因此在 内部\protected@edef
, 的扩展\protect
是\noexpand\protect\noexpand
,例如。)
宏\DeclareRobustCommand
在 LaTeX2e 中可用。这会在宏本身中添加一些自动保护,因此\protect
不需要这样做。这在某些\protected@write
情况下也有效。
这很好,但是更好的方法是 e-TeX 的\protected
系统:
\protected\def\foo....
以这种方式定义的宏根本不会在或内部展开\edef
,\write
因为引擎本身知道不要管它们。这是etoolbox
和xparse
用于定义真正强大的宏。受引擎保护的宏完全不依赖 LaTeX2e 的机制,因此在普通的 中是安全的\edef
。
答案3
这个问题也许最好通过一个例子来回答。考虑一下fragrance命令\title
。以下是来自的相关定义latex.ltx
:
\def\title#1{\gdef\@title{#1}}
\def\@title{\@latex@error{No \noexpand\title given}\@ehc}
\title{This is the title}
现在想象一下,您在命令的参数中包含一个首先执行此参数的命令,然后将其写入辅助文件,或以其他方式“移动”它。我们可以运行一个交互式实验来查看会发生什么,使用\edef
立即查看结果:
; latex
This is pdfTeX, Version 3.1415926-1.40.11 (TeX Live 2010)
restricted \write18 enabled.
**\relax
[…]
*\title{This is the title}
*\edef\foo{\title{This is the title}}
*\show\foo
> \foo=macro:
->\gdef This is the title{This is the title}.
<*> \show\foo
正如您所想象的(如果必须,请尝试一下),实际执行\foo
不会像您在运行此实验之前所预期的那样进行。当然,问题是这个问题\@title
扩大了。
要想知道它是如何\protect
工作的,你可以做比跑步更糟糕的事情texdoc source2e
并查看第 11.4 节,强大的命令和保护。
答案4
一些评论。
注意旧式强命令。
因为历史原因(?),一些命令是“旧式健壮的”(通常是在 e-TeX 可用之前写回的命令),一些是“新式健壮的”(使用 e-TeX 的\protected
机制)。另请参阅这个问题
你可以分辨出哪一个是旧式健壮,哪一个是新式健壮,例如:
$ latexdef textbf
\textbf:
macro:->\protect \textbf
\textbf :
\long macro:#1->\ifmmode \nfss@text {\bfseries #1}\else \hmode@bgroup \text@command {#1}\bfseries \c
heck@icl #1\check@icr \expandafter \egroup \fi
如果有\protect
,那么它就是旧式健壮的。
只有少数新型强健命令可用:
$ latexdef lfloor
\lfloor:
\protected macro:->\delimiter "4262304
$ latexdef AddToHook
\AddToHook:
\protected macro:->\__cmd_start:nNNnnn {mo+m}\AddToHook \AddToHook code {\__cmd_grab_m_1:w \__cmd_g
rab_D:w []\__cmd_grab_m_long:w }{}{}
您可以通过 来查看它们\protected
。
问题是,如果某个命令是老式的健壮命令,那么不能在内部使用\write
或\edef
。
相反,分别有\protected@write
和\protected@edef
。但是没有\protected@immediate@write
,您需要自己定义一个。
命令如\hyphenation
:
$ latexdef \hyphenation
\hyphenation:
\hyphenation
是“坚固的”(不可扩展的),因为它们是TeX 原语,所以他们在任何地方都是安全的。
对于用户定义的命令,\NewDocumentCommand
会产生新式健壮命令,(几乎)其他所有内容都会产生旧式健壮命令。
$ latexdef -o '\DeclareRobustCommand\test{hello}' test #% this is old-style robust because it has "\protect"
\test:
macro:->\protect \test
\test :s?
\long macro:->hello
$ latexdef -o '\newcommand\test{hello}\MakeRobust\test' test #% identical to the above
\test:
macro:->\protect \test
\test :
\long macro:->hello
$ latexdef -o '\NewDocumentCommand\test{}{hello}' test #% this is new-style robust because it has "\protected macro"
\test:
\protected macro:->\__cmd_start_expandable:nNNNNn {}\test \test \test code ?{}
$ latexdef -p etoolbox -o '\newrobustcmd\test{hello}' test #% this is new-style robust
\test:
\protected\long macro:->hello
$ latexdef -o '\newcommand\test{hello}' test #% this is not robust
\test:
\long macro:->hello
并非所有命令都是脆弱的或强大的。
有以下几种命令我们不会说它们是脆弱的或者健壮的:
\verb
或类似的命令,无论如何,它在命令参数中都不会起作用\protect
。(备注:这些命令改变了猫码某些字符。您需要
cprotect
或类似的,或者只需切换到等效的非 catcode 更改命令,例如\texttt
)完全可扩展的命令,即旨在“返回结果”而不是排版/执行某些操作的命令。
无论是在仅扩展上下文还是在执行上下文中,此功能始终都会起作用。
定义。
(以“总体概述”术语来说,而不是以 TeX 原语术语来说。)
上面的答案很好地定义了脆弱的命令。
某些宏的正确操作依赖于在下一个标记展开之前正确执行某些操作。这些宏被称为“脆弱的”,因为它们仅在正常(交错)模式下工作,而不在仅展开的上下文中工作(例如“移动参数”,这通常意味着写入文件)。
换句话说,如果
- 它在排版环境中起作用
- 在仅扩展环境中使用时,它无法按预期工作。
相反,一个强有力的命令
- 在排版环境中工作
- 根本没有扩展在仅扩展上下文中。
(请记住,完全可扩展的命令既不脆弱也不强大。)
每个脆弱的命令都可以变得强健。
因此,原则上,使命令变得健壮并不是很难——只需延迟扩展它,直到它处于执行上下文中。
(作为比较,并非所有不可扩展的命令都可以扩展。)
如果你遇到“神秘错误”一些 LaTeX 内置命令,这是因为例如 LaTeX 团队没有使其变得健壮,很可能是因为历史原因。
(几十年前,需要一些额外的内存字节来增强命令的可靠性,而机器的内存是有限的。如今大多数命令已经变得强大,所以这种情况希望很少见。)
如果这是您自己的命令,请使用\DeclareRobustCommand
或\NewDocumentCommand
代替\def
或\newcommand
希望能解决问题。
或者如果它与其他一些库一起定义,例如tcolorbox 的\newtcbox
其中\NewDocumentCommand
不是一个选项,而是\MakeRobust
(参见上面链接的答案中的示例。文档不知道在哪里,但它是 LaTeX 内置的),或etoolbox
的\robustify
。
为什么必须\section
充分展开其论点?
或者:\section
如果只是将内容写入未扩展的文件中,然后稍后执行它,不是更简单吗?
以前,我认为这是不可能的。(至少没有一些额外的编程努力,而且用 TeX 编程很困难。)
如今,在 e-TeX 中可以 \detokenize
写入文件之前的参数,不会扩展任何内容;但是......
作为埃格指出除非用户明确注入扩展值,否则这将导致用户定义的计数器的值不正确,而这并不容易。
还引用了 Cyrus 在该答案下面的评论
TeX 允许用户扩展,但如果用户不想要,他们可以选择不扩展,使用
\protect
或\DeclareRobustCommand
。默认是扩展,所以需要额外的工作如果用户有其他需要的话。