如何逃避历史扩展感叹号!在双引号字符串中?

问题描述 投票:0回答:5

编辑:尽管这是最常见的用例,但对于令人惊讶的行为来说,命令替换不是必需的。同样的问题只适用于

echo "'!b'"

b=a

# Enable history substitution.
# This option is on by default on interactive shells.
set -H

echo '!b'
# Output: '!b'
# OK. Escaped by single quotes.

echo $(echo '!b')
# Output: '!b'
# OK. Escaped by single quotes.

echo "$(echo '$b')"
# Output: '$b'
# OK. Escaped by single quotes.

echo "$(echo '!b')"
# Output: history expands
# BAD!! WHY??
  • 在最后一个例子中,逃避
    !
    的最好方法是什么?
  • 为什么即使我使用单引号也没有转义,而
    echo "$(echo '$b')"
    是?
    !
    $
    和有什么不一样?
  • 为什么
    echo $(echo '!b')
    (无引号)有效? (由@MBlanc 指出)

我宁愿这样做没有:

  • set +H
    因为我需要
    set -H
    之后保持外壳状态

  • 反斜杠转义,因为每个

    !
    都需要一个反斜杠,而且它必须在引号之外:

    echo "$(echo '\!a')"
    # '\!a'.
    # No good.
    
    echo "$(echo 'a '\!' b '\!' c')"
    # a ! b ! c
    # Good, but verbose.
    
  • echo $(echo '!b')
    (无引号),因为该命令可能会返回空格。

版本:

bash --version | head -n1
# GNU bash, version 4.2.25(1)-release (i686-pc-linux-gnu)
bash syntax quotes
5个回答
23
投票

在你的最后一个例子中,

echo "$(echo '!b')"

感叹号不是单引号。因为历史扩展发生在解析过程的早期,所以单引号只是双引号字符串的一部分;解析器尚未识别命令替换以建立新的上下文,其中单引号将引用运算符。

要修复,您必须暂时关闭历史扩展:

set +H
echo "$(echo '!b')"
set -H

4
投票

这被反复报告为一个错误,最近一次是在 2014 年针对 bash 4.3,因为行为可以追溯到 bash 3

讨论了这是否构成错误或预期但可能不受欢迎的行为;似乎已经达成共识,无论你想描述这种行为的特征,都不应该允许它继续下去。

它在 bash 4.4 中已修复,

echo "$(echo '!b')"
不展开,
echo "'!b'"
展开,我认为这是正确的行为,因为单引号在第一个示例中是 shell 语法标记,而在第二个示例中不是。


2
投票

如果启用了历史扩展,您只能回显

!
字符,前提是它被放在单引号中、转义或后跟空格字符、回车符或
=

来自

man bash

   Only backslash (\) and single quotes can  quote  the  history
   expansion character.

   Several  characters inhibit history expansion if found immediately fol-
   lowing the history expansion character, even if it is unquoted:  space,
   tab,  newline,  carriage return, and =.

我相信这里的关键词是“只有”。问题中提供的示例仅考虑最外层的引号是双引号。


2
投票

有时你需要对一个大的命令管道做一个小的补充

OP 的“很好,但冗长”的例子在很多情况下实际上非常棒。

请原谅人为的例子。我需要这样一个解决方案的全部原因是我有很多分散注意力的嵌套代码。但是,归结为:我必须在双引号 bash 命令扩展中的

!d
中执行
sed

这个有效

$ ifconfig | sed '/inet/!d'
inet 127.0.0.1 netmask 0xff000000
…

这不

$ echo "$(ifconfig | sed '/inet/!d')"
-bash: !d': event not found

这是最简单的妥协

$ echo "$(ifconfig | sed '/inet/'\!'d')"
inet 127.0.0.1 netmask 0xff000000
…

使用折衷方案,我可以在现有代码中插入一些字符,并生成一个任何人都能理解的合并请求……即使生成的代码更难理解。如果我做了一个完整的重构,代码审查者将有更多的时间来验证它。当然,这个 bash 没有单元测试。


0
投票

通过关闭双引号,将

!
放在单引号
'
之后(前后没有空格)然后再次打开双引号:

$ echo "this is "'!'"how you do it"
this is !how you do it
© www.soinside.com 2019 - 2024. All rights reserved.