编辑:命令替换对于令人惊讶的行为不是必需的,尽管它是最常见的用例。同样的问题仅适用于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
来保持shell状态
反斜杠转义,因为每!
我需要一个,它必须在引号之外:
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)
答案 0 :(得分:20)
在上一个例子中,
echo "$(echo '!b')"
感叹号不是单引号。因为历史扩展在解析过程的早期发生,所以单引号只是双引号字符串的一部分;解析器还没有识别命令替换,以建立一个新的上下文,其中单引号将引用运算符。
要修复,您必须暂时关闭历史记录扩展:
set +H
echo "$(echo '!b')"
set -H
答案 1 :(得分:4)
有人讨论这是一个错误或预期但可能是不良行为;似乎已经达成共识,但是你想要表征这种行为,不应该让它继续下去。
它已在bash 4.4中修复,echo "$(echo '!b')"
不会展开,echo "'!b'"
会这样做,我认为这是正确的行为,因为单引号是第一个示例中的shell语法标记,而不是第二个示例中的shell语法标记。
答案 2 :(得分: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 =.
:
{{1}}
我相信这里的关键词是“只有”。问题中提供的示例仅将最外面的引号视为双引号。
答案 3 :(得分:0)
在很多情况下,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
…
使用折衷方案使我可以在现有代码中插入几个字符,并生成任何人都可以理解的Pull请求……即使生成的代码更难以理解。如果我进行了完整的重构,则代码审阅者将需要更多的时间来验证它。当然,这个bash没有单元测试。