为什么感叹号“!”有时会扰乱 bash?

为什么感叹号“!”有时会扰乱 bash?

我意识到这!在命令行历史记录的上下文中对命令行具有特殊意义,但除此之外,在运行脚本中感叹号有时会导致解析错误。
我认为这与 a 有关event,但我不知道 a 是什么事件是什么或它做什么。即便如此,相同的命令在不同的情况下也可能有不同的行为。
下面的最后一个示例会导致错误;但是为什么当相同的代码在命令替换之外工作时呢? ..使用 GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found

答案1

!角色调用 bash 的历史替换(在交互式 shell 中默认启用)。当后跟一个字符串时(如您的失败示例中所示),它会尝试扩展到以该字符串开头的最后一个历史事件。就像$var扩展为该字符串的值一样,!echo将扩展为历史记录中的最后一个 echo 命令。

空间是此类扩展中的一个破坏性特征。首先注意这将如何处理变量:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

历史扩展也会发生同样的事情。感叹号 ( !) 开始历史替换序列,但前提是后跟字符串。后面加一个空格,使其成为字面意义的爆炸,而不是替换序列的一部分。

您可以通过使用单引号来避免变量和历史扩展的这种替换。您的第一个示例使用单引号,因此运行良好。您的最后一个示例用双引号引起来,因此 bash 在执行其他操作之前会扫描它们以查找扩展序列。第一个没有跳闸的唯一原因是空格是一个中断字符,如上所示。

答案2

正如已经迦勒说!用于调用 bash 的历史替换。

如果像我一样,您觉得不需要这样的功能,您可以通过插入以下行来禁用它~/.bashrc

set +H

我不需要它,因为可以通过向上箭头和Ctrl-r增量反向搜索来恢复历史记录。请参阅 bash 的手册页,部分操纵历史的命令了解快捷方式的详细列表。

答案3

你的第一个例子:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

可以减少到

echo '! p' 
echo '!p'

在单引号内,所有字符都保留其字面值。从而!失去了其特殊的意义,历史的展开也没有进行。

你的第二个和第三个例子:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

可以减少到

echo "'! p'"

echo "'!p'"

'! p''!p'本质上是双引号字符串的一部分。

在双引号内,所有字符都保留其字面值除了 $`\!

'! p'这意味着和的单引号'!p'已经失去了它们的特殊含义(即:无法转义!),但!仍然保留其特殊含义,从而执行历史扩展。

然而,当!后面跟有空格字符时,不执行历史扩展。

引用自man bash

报价

[...]

将字符括在单引号中可保留引号内每个字符的字面值。 [...]

将字符括在双引号中会保留引号内所有字符的字面值,但 $、`、\ 以及启用历史扩展时的 ! 除外。 [...] 如果启用,将执行历史扩展,除非 !出现在双引号中的内容使用反斜杠进行转义。 ! 前面的反斜杠没有被删除。

历史扩展

[...]

历史扩展是通过历史扩展角色的出现来引入的,那就是!默认情况下。只有反斜杠 (\) 和单引号可以引用历史扩展字符。

如果紧跟历史扩展字符,即使未加引号,也有几个字符会抑制历史扩展:空格、制表符、换行符、回车符和 =。如果启用了 extglob shell 选项,( 也会抑制扩展。

相关内容