不能在bash中使用感叹号(!)?


86

我正在尝试使用curl命令访问!其路径中带有感叹号()的http url 。例如:

curl -v "http://example.org/!287s87asdjh2/somepath/someresource"

控制台会回复bash: ... event not found

这里发生了什么?逃脱感叹号的正确语法是什么?


解决于bash 4.4+
Isaac

Answers:


98

感叹号是bash历史扩展中的一部分。要使用它,您需要将其用单引号引起来(例如:),'http://example.org/!132'或直接\在字符前面(例如:)加上反斜杠()将其转义"http://example.org/\!132"

请注意,在双引号中,感叹号之前的反斜杠会阻止历史扩展,但在这种情况下,不会删除反斜杠。因此,最好使用单引号,这样就不会curl在URL中传递文字反斜杠。


8
"http://example.org/\!132"实际上扩展时没有解释反斜杠(我相信是POSIX合规性的原因)。
克里斯·

@ChrisDown,我试图澄清这是文本中的第二个选项。感谢您指出混淆的可能性。
丹尼尔·皮特曼

6
记录:尝试转义“!”不是可移植的。最佳做法建议是始终用单引号(!)引号!相关:“ ^”(脱字符号),是非字符,需要引用才能实现可移植性。最后,“!” 不应在if语句中使用;尽可能使用它作为参数进行测试(同样由于Solaris / bin / sh)。
尼古拉斯·威尔逊

5
只有单引号对我有用。zsh仍在解释\!和双引号。
Orkoden 2014年

1
在Solaris(旧的XPG4之前的旧外壳程序)上,“ ^”是别名,|用于创建管道。如果您要向客户发送脚本,但不确定他们将在哪个shell中运行脚本,则必须对它们全部进行测试!
尼古拉斯·威尔逊

61

除了Daniel给出的答案外,如果您不使用,也可以完全关闭历史记录扩展set +H


19
整天关闭历史记录扩展是我整天听到的最好建议!历史扩展是危险的,拜占庭的时候有更好的替代品(增量历史搜索用Ctrl-R),让你预览和编辑您的命令,所以你千万不要盲目使用命令离火!-14,你虽然是在!-12说,哎呀,碰巧rm -rf *。注意安全。禁用历史记录扩展!避免!
aculich'3

6
最大的答案:历史扩展是巨大的安全风险!它可以用来通过特制的URL攻击Unix。
2015年

@aculich,或仅使用POSIX指定的命令fc -14代替。 但是,确实可以在不启用历史记录扩展的情况下执行此操作。就个人而言,我用!$!visudo !!,甚至git add !vi:$往往不足以保证能留下历史扩展。
通配符'18

我想将其添加到我的Shell RC文件中。我只用过它作为一个巧妙的“技巧”
TonyH

17

我个人会用单引号引起来,但是为了完整起见,我还要指出,由于它是一个URL,因此可以将!as 编码为%21,例如curl -v http://example.org/%21132


13

这也可以

curl -v "http://example.org/"'!'"287s87asdjh2/somepath/someresource"
要么
curl -v "http://example.org/"\!"287s87asdjh2/somepath/someresource"

之所以有效,是因为bash连接了相邻的字符串。当您还有其他需要外壳扩展的内容时,此方法特别有用,因此您不能对整个字符串使用单引号:

curl -v 'http://example.org/!'"287s87asdjh2/${basepath}/someresource"

!字符用于命令行提示符中的历史记录扩展。
因此,这可能在提示符下出现问题,但在shell脚本文件中却没有。
如您所见,历史记录扩展即使在双引号中也有效。


有很多方法可以使Unix命令和英语句子使用比他们需要的字符更多的字符,并使它们比需要的字符更加混乱。这比第一个/接受的/投票最高的答案(即将整个URL放在单引号中)有什么优势?
G-Man

2
@ G-Man:它告诉了构造bash参数的另一种方法。我不知道这种方法。学习新东西没错。
萨希尔·辛格

@SahilSingh这是新的吗?它连接三个字符串,两个字符串用双引号引起来,一个字符串用单引号引起来。这里没有嵌套。
拉斐尔

@ G-Man并不明显,当您将2个字符串彼此相邻放置时,它们会串联在一起。printf(“ hello”“ world”)也会在c中工作,但是printf(“ hello”'w')不会工作,所以您知道知道bash可以容纳这样的表达式对我来说是新的,但是我同意效用来看,这并不优越。我喜欢这个答案,马克·舒斯特也一样。
萨希尔·辛格

2
@ G-Man当确实要在同一字符串中进行其他字符串扩展时,它也很有用。这是分离两种类型的报价行为的简便方法。
WAF

7

我遇到了同样的问题,我的简单解决方案是使用变量:

E=!  
curl -v "http://example.org/${E}287s87asdjh2/somepath/someresource"

这里的简单之处在于(1)它可以跨shell和命令移植(2)不需要知道转义语法和ASCII代码。


3

从Bash 4.3开始,您现在可以使用双引号来引用历史记录扩展字符:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!

这在外部不起作用echo,echo似乎独自处理了这一问题
phil294 '17

@Blauhirn这与echo无关,与引用和正在运行的bash版本无关。
Flimm

2
这个答案是错误的,应该删除。您的bash版本与爆炸没有得到扩展没有关系,这是由于在您的示例中!,“”后面紧跟着“行尾”,并且阻止了shell尝试扩展它。试试看 echo "!Hello World",您会看到bash会回复bash: !Hello: event not found有关更多详细信息,请参见手册
don_crissti

0

对于在Windows中使用git bash的用户,@ DanielPittman可接受的答案有效。但是,应将反斜杠(\)替换为正斜杠(/)。

例如,在Unix中,它看起来像这样:

curl https://abc.com/services -H 'Authorization: Bearer 111A80BBZZCnS\!ZR412543s'

对于Windows,可能是这样的(关注授权标头部分中的正斜杠)

curl https://abc.com/services -H 'Authorization: Bearer 111A80BBZZCnS/!ZR412543s'


这没有多大意义。您使用单引号将参数引起来,因此无论是否包含斜杠,感叹号都不会导致历史扩展。
通配符'18

哦,对了。我之所以仅发布此答案,是因为当我使用Daniel的答案(使用反斜杠)时,会弹出一个错误。
SamuelDev
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.