为什么感叹号`!`有时会使bash不高兴?


14

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


@沃伦..谢谢。我见过QA,但实际上只讨论了如何转义反斜杠...我的问题更多地是关于为什么看似已经转义的代码在一种情况下而不在另一种情况下工作...
Peter.O 2011年

@fred:“似乎已经逃脱了”?我根本看不到任何转义符,并且您使用的是双引号。请参阅我的(修订)答案。您认为哪一部分逃脱了?
Caleb,

@Caleb。是的,我使用了错误的术语protected。(由“单引号”保护)
Peter.O 2011年

如果只关心简单的赋值,则可以使用var=$(…)(不带双引号),它将按您期望的那样工作(我认为)。这仍然是“安全的”,因为一个简单的分配的值部分是不受分词或通配符(尽管这可能不是通过内建进行(例如分配的真实exportlocal所有的炮弹下等))。不幸的是,这并不超出简单的赋值,因为双引号是防止单词分裂和模糊的方法,同时在其他情况下仍可以进行其他类型的扩展。
克里斯·约翰森

Answers:


12

!字符调用bash的历史替换。当后面跟一个字符串时(如您的失败示例),它尝试扩展到以该字符串开头的最后一个历史事件。就像$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在执行其他操作之前先扫描了它们的扩展序列。第一个未跳闸的唯一原因是空格是一个上图所示的分隔符。


谢谢Caleb。。我的另一个先入之见被驳回了……我认为bash解析是从最里面的括号或大括号进行的,然后向外进行……bash的解析似乎与我的假设不同。
Peter.O 2011年

1
如果不更改嵌套字符串,bash中的引用就很容易混淆。实际上,替换过程很早就发生了。考虑以下示例:var=word; echo "test '$var'"; echo 'test "$var"'
Caleb,

..是的。我知道在引号中嵌套引号。我的误解是我认为命令替换括号内的代码将被单独解析为包围这些括号的内容。但显然不是..谢谢。
2011年

6

正如Caleb所说!用于调用bash的历史记录替换。

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

set +H

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


2
你如何没有生活!!
Caleb,

谢谢。我认为这可能是个问题,从便携性角度来看,但是set +H在脚本中使用同样有效:) +1
Peter.O 2011年

2
@fred:奇怪,通常历史记录扩展仅在交互式shell上处于“打开”状态。
enzotib

@enzo ..再次感谢..我已经从命令行进行了测试..啊!如果学习不是那么有趣,那将是乏味的……我提到咖啡了吗?它也有帮助:)
Peter.O 2011年

是的,这是一个陷阱。正是由于这个原因,我从命令行失败的脚本中复制了粘贴的代码。历史扩展在脚本中不是问题,但它是在交互式shell上。
Caleb,

2

您的第一个示例:

{ 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选项,(也会抑制扩展。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.