深度嵌套命令

Deeply nesting commands

为了转义 bash 中的字符,为什么在深度嵌套命令时语法会令人困惑?,我知道有一种使用 $() 嵌套命令的替代方法,只是好奇,为什么会这样使用反引号嵌套命令时!

例如:

echo `echo \`echo \\`echo inside\\`\``

给出输出:inside

但是

echo `echo \`echo \`echo inside\`\``

失败,

bash: command substitution: line 1: unexpected EOF while looking for matching ``'
bash: command substitution: line 2: syntax error: unexpected end of file
bash: command substitution: line 1: unexpected EOF while looking for matching ``'
bash: command substitution: line 2: syntax error: unexpected end of file
echo inside\

我的问题是,为什么二级嵌套所需的反斜杠数量是3而不是2。在上面给出的例子中,一个反斜杠用于一层深度,三个用于二级嵌套命令以保留反引号的字面意思。

您所展示的语法本身并没有令人困惑的地方。你只需要一个一个地分解每个级别。

GNU bash 手册页说

When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by $, `, or \.

Command substitutions may be nested. To nest when using the backquoted form, escape the inner backquotes with backslashes.

因此在上下文中,嵌套替换有一个 \ 来转义反引号,还有一个来转义转义字符(现在阅读上面的引述 \ 失去了它的特殊性意思是除非后面跟着另一个\)。所以这就是第二层转义需要两个额外的反斜杠来转义原始字符的原因

echo `echo \`echo \\`echo inside\\`\``
#                 ^^^^           ^^^^    

变成

echo `echo \`echo inside\``
#          ^^           ^^

这又变成了

echo `echo inside`
#    ^           ^

最终变成

echo inside

基本问题是开反引号和闭反引号之间没有区别。所以如果 shell 看到这样的东西:

somecommand ` something1 ` something2 ` something3 `

...没有内在的方法来判断这是否是两个单独的反引号命令(something1something3),中间有一个文字字符串("something2");或嵌套的反引号表达式,其中 something2 首先是 运行,其输出作为参数(连同文字字符串 "something3")传递给 something1。为了避免歧义,shell 语法选择了第一种解释,并要求如果你想要第二种解释,你需要转义反引号的内层:

somecommand ` something1 ` something2 ` something3 `   # Two separate expansions
somecommand ` something1 \` something2 \` something3 ` # Nested expansions

这意味着添加另一个级别的解析和删除转义,这意味着您需要转义您当时不想解析的任何转义,并且整个事情很快就会失控。

另一方面,$( ) 语法没有歧义,因为开始和结束标记不同。比较两种可能性:

somecommand $( something1 ) something2 $( something3 )   # Two separate expansions
somecommand $( something1 $( something2 ) something3 )   # Nested expansions

那里没有歧义,所以不需要转义或其他语法怪异。

越狱次数随着关卡数量增长如此之快的原因,也是为了避免歧义。而且它不是特定于带反引号的命令扩展的东西;这个 escape inflation 会在你有一个字符串经过多级解析时出现,每个解析都适用(并删除)转义。

假设 shell 运行 在解析一行时跨越两个转义符和一个反引号 (\`)。它应该将其解析为双重转义反引号,还是单次转义转义(反斜杠)字符后跟一个完全不转义的反引号?如果它 运行s 跨越三个转义和一个反引号 (\\`),那么是三次转义反引号、两次转义转义后跟一个完全不转义的反引号,还是单-转义后跟一个单独转义的反引号?

shell(像大多数处理转义的东西一样)通过不将堆叠转义视为特殊事物来避免歧义。当它 运行 变成一个转义字符时,它只适用于 紧随其后的东西;如果紧随其后的是另一个转义符,那么它会转义那个字符并且对它后面的任何内容都没有影响。因此 \` 是一个转义的转义,后面跟着一个完全没有转义的反引号。这意味着您不能只在前面添加另一个转义符,您必须在字符串中每个值得转义的字符前面添加一个转义符(包括来自较低级别的转义符)。

所以,让我们从一个简单的反引号开始,然后将其转义到不同的级别:

  • 第一关很简单,逃吧:\'.
  • 对于第二个级别,我们必须转义那个转义符(\)然后单独转义反引号本身(\`),总共三个反引号:\\` .
  • 对于第三级,我们必须单独转义这三个转义(所以 3x\)并再次转义反引号本身(\`),总共有七个反引号: \\\\`.

就这样继续下去,每个级别的逃脱次数增加一倍以上。从 7 到 15,然后是 31,然后是 63,然后......人们试图避免深层嵌套转义的情况是有充分理由的。

哦,正如我提到的,shell 并不是唯一这样做的东西,这会使事情复杂化,因为不同的级别可以有不同的转义语法,有些东西可能不会 需要 在某些级别转义。例如,假设被转义的是正则表达式\s。要为其添加一个级别,您只需要一个额外的转义符 (\s),因为 "s" 不需要自己转义。额外的转义级别将提供 4、8、16、32 等转义。

TLDR;哟,老兄,我听说你喜欢逃跑...

P.s。您可以使用 shell 的 -v 选项使其在执行命令之前打印命令。使用这样的嵌套命令,它会在取消嵌套命令时打印每个命令,因此您可以在层被剥离时看到堆栈转义转义崩溃:

$ set -v
$ echo "this is `echo "a literal \`echo "backtick: \\\\`" \`" `"
echo "this is `echo "a literal \`echo "backtick: \\\\`" \`" `"
echo "a literal `echo "backtick: \\`" `" 
echo "backtick: \`" 
this is a literal backtick: `

(为了更有趣,在 set -vx 之后试试这个 -- -x 选项将打印命令 after 解析,所以在你看到它之后深入了解嵌套命令,然后您将看到当它展开回到最终的顶级命令时会发生什么。)