历史扩展中的单引号 (bash)

Single quotes in history expansion (bash)

我有一个关于 Bash 语法的理论问题。
我在 Linux Ubuntu 14.04.

中运行 Bash 4.3.11(1)

GNU 官方网站:Bash official web (GNU)
在第 9.3.1 节中。它说:

!string

Refer to the most recent command preceding the current position in the history list starting with string.

一般来说,从句法上讲,string 是在第一个空格或换行符之前结束的字符序列。

然而,在3.1.2.小节中描述引用时,我们可以在3.1.2.2段中阅读。接下来是:

Enclosing characters in single quotes (‘'’) preserves the literal value of each character within the quotes.

特别是,单引号内的空格不会破坏分隔词中的字符串。

因此,像 !'some text' 这样的表达式必须在 Bash 的历史列表中搜索以 'some text' 开头的最新命令。

但是,当我在我的终端中写入时,sometext 之间的空白被打破了,因为显示了以下错误消息:

bash: !'some: event not found

此行为是 shell 实现中的错误,还是我不理解此示例的 Bash 的扩展规则?

我不会将观察到的行为称为错误,因为除了观察到的 bash shell 本身的行为外,没有针对历史扩展的规范。但可以肯定的是,解析历史扩展表达式的精确机制没有得到很好的记录,并且有很多可能令人惊讶的极端情况。

bash 联机帮助页确实说明了历史扩展 "is performed immediately after a complete line is read, before the shell breaks it into words"(强调已添加),而 bash 手册提到历史扩展是由历史库提供的。这是大多数历史扩展解析异常的根本原因:历史扩展在原始未解析输入上工作,没有任何来自 bash 分词器的帮助,并且主要是通过一个不是 bash- 的外部库完成的具体的。由于标记化 bash 输入非常重要,因此历史扩展期间使用的相对简单的解析规则只是对真实 bash 解析的粗略近似也就不足为奇了。

例如,bash 手册确实指出您可以通过反斜杠引用来防止历史扩展字符 (!) 被识别。但没有明确记录任何紧接在 ! 之前的 \ 将禁止识别历史扩展,即使反斜杠本身被引用一个反斜杠。因此 \!word 中的 ! 而不是 会导致替换以 word 开头的先前命令。 (\word是执行命令word而不是别名word的常用方式,所以这个例子并不完全是人为的。)

可以在 this answer.

中找到对历史扩展字符识别的一些极端情况的详细讨论。

这个问题提出的问题略有不同,因为它是关于历史扩展解析的下一阶段。一旦确定某个特定字符是历史扩展字符,就需要解析后面的 "event";如 bash 手册所述,事件可以采用多种形式,其中一种是 !string,表示以“string”开头的最新命令。

暗示只有在没有其他形式适用时才会使用此形式,这意味着 string 可能不以数字开头或 -, !#?。它也可能不以空格或 = 开头(因为这些会抑制历史扩展)并且在某些情况下 " =88=](这可能会抑制历史扩展)。最后,它可能不会以 ^$% 开头*,将被解释为单词指示符(来自默认事件,即上一个命令)。

bash manual does not specify what terminates the string. It is semi-documented in the history library manual,其中提到历史搜索字符串(或 bash 手册中所称的 "event")以空格终止,,或历史配置变量 history_search_delimiter_chars 中的任何字符。 (根据记录,bash 当前 (v4.3) 将该变量设置为 ";&()|<>"。)

如前所述,在决定是否识别历史扩展字符时会考虑引用;事实证明,如果历史扩展发生在双引号字符串内,则结束双引号也被视为历史搜索定界符。据我所知,这是分隔 !string.

的整个字符列表

bash 和历史文档中都没有说明历史搜索定界符可以通过引号变为非特殊字符,事实上这并没有发生。一个开引号,无论是双引号还是单引号,甚至是 之后的反斜杠 ! 都将被视为 string 的一部分被搜索到,不做任何特殊处理。

子字符串匹配历史扩展的解析——!?string?——完全不同。该字符串只能由 ? 或换行符终止。 (正如 bash 手册所说,尾随的 ? 是可选的,如果以换行符终止。)

一旦识别出历史扩展字符并识别出历史搜索字符串,则可能需要将检索到的历史条目拆分为单词。同样,bash 手册对边角情况略显漫不经心,它说 "the line is broken into words in the same fashion that Bash does, so that several words surrounded by quotes are considered one word."

书呆子会观察到 "in the same fashion that Bash does" 与说 "exactly as Bash would do" 并不完全相同,事实上句子的第二部分是字面上的真实:几个被引号包围的词被认为是一个词即使引号不是真正匹配的引号。例如,行:

command "$(echo " foo bar ")"

被历史图书馆认为由以下五个字组成:

0. command
1. "$(echo "
2. foo
3. bar
4. ")"

尽管 bash 解析会完全不同。相比之下,bash和历史库同意

的解析
command "$(echo ' foo bar ')"

作为两个词。