完全引用

完全引用

我问为什么这三个命令给出三个不同的答案:

$ printf "%s\n"   `echo ../.. | sed 's/[.]/\\&/g'`
&&/&&
$ printf "%s\n"  $(echo ../.. | sed 's/[.]/\\&/g')
../.. 
$ printf "%s\n" "$(echo ../.. | sed 's/[.]/\\&/g')"
\.\./\.\.

答案1

这是一个很难回答的问题。最后一个解释的最简单的例子。

完全引用

好吧,实际上,一个完整引用的例子:

$ printf "%s\n"    "$(   echo "../.." | sed 's/[.]/\\&/g'   )"
\.\./\.\.

这里 shell 没有做任何技巧或更改,因为所有内容都被引用了。最内部的内容echo "../.."被引用,因此不受文件名扩展的影响。它不变地进入 sed,并且 sed 将每个点更改为\&.然后该命令的结果也被引用,"$(...)"这(再次)避免了 shell 的任何更改,并且该printf命令还打印\.\./\.\..这里没有什么惊喜。

改成 ##/##

如果存在文件名扩展(通配符),../..我们无论如何都会结束../..。所以,最后的字符串是相同的。但让我们测试一下这个问题:

$ echo ../../t*
../../test2.txt ../../tested ../../test.txt

$ set -f     # remove all filename expansion

$ echo ../../t*     ../..
../../t* ../..

*而且,无论如何,不​​包含 a 、 a?或 a 的字符串[不受模式匹配表示法

证明:设置 GLOBIGNORE

$ (GLOBIGNORE='.*:..*'; echo "any" ../../t* ../.. "value")
any ../.. value

如果../..受到 globbing(文件名扩展)的影响,那么它将由于 GLOBIGNORE 的值而被删除。

但是,为了确保不存在文件名扩展,我们可以切换到(最有可能)不存在的文件名:##/##

不带引号的命令执行

shell 没有理由删除 a \,即使(命令执行)不加引号:

$ printf '%s\n' $(printf '%s\n' '\#\#/\#\#')
\#\#/\#\#

事实上,我测试的所有 shell 都没有显示您在第二个示例中报告的内容。 (请纠正我!)。

编辑:在 bash 5.0.17 中存在一个错误。但仅限于点。

$ b50sh
$ echo $BASH_VERSION
5.0.17(3)-release

$ printf '%s\n' $(printf '%s\n' '\.\./\.\.')
../..

$ printf '%s\n' $(printf '%s\n' '\#\#/\#\#')
\#\#/\#\#

$ printf '%s\n' $(printf '%s\n' '\>\>/\>\>')
\>\>/\>\>

$ exit
$ echo $BASH_VERSION
5.1.4(1)-release

$ printf '%s\n' $(printf '%s\n' '\.\./\.\.')
\.\./\.\.

$ printf '%s\n' $(printf '%s\n' '\#\#/\#\#')
\#\#/\#\#

并且似乎已经在 bash 5.1.4 中解决了

反引号

这是最棘手的问题:里面的反引号 every 都\\变成了\.

因此,sed 命令(如 sed 所示)是:s/[#]/\&/g。要执行我认为您的意思的操作,您需要复制 s \

$ printf '%s\n'  `printf '%s\n' '##/##' | sed 's/[#]/\\&/g'`
&&/&&

$ printf '%s\n' "`printf '%s\n' '##/##' | sed 's/[#]/\\&/g'`"
&&/&&

$ printf '%s\n' "`printf '%s\n' '##/##' | sed 's/[#]/\\\\&/g'`"
\#\#/\#\#

答案2

在 bash 版本 5.0.17 中,man bash注意到两种形式的命令替换之间的以下区别:

   When  the  old-style  backquote form of substitution is used, backslash
   retains its literal meaning except when followed by $, `,  or  \.   The
   first backquote not preceded by a backslash terminates the command sub‐
   stitution.  When using the $(command) form, all characters between  the
   parentheses make up the command; none are treated specially.

set -x如果您在 shell 中,您可以看到差异:

$ set -x

$ printf "%s\n"   `echo ../.. | sed 's/[.]/\\&/g'`
++ echo ../..
++ sed 's/[.]/\&/g'
+ printf '%s\n' '&&/&&'
&&/&&

显示\\&被折叠到\&(因为\ 其次是\); the&失去了 sed 中的特殊含义,因此..变成&&, while (新样式)

$ printf "%s\n"  $(echo ../.. | sed 's/[.]/\\&/g')
++ echo ../..
++ sed 's/[.]/\\&/g'
+ printf '%s\n' ../..
../..

两个反斜杠都按字面意思传递给 sed,sed 用..\.\.\\字面反斜杠,并&保留其特殊含义)替换;当未引用的然后结果由 shell 回显,../..当引用的命令替换逐字打印 sed 输出时,您会得到\.\./\.\.

$ printf "%s\n" "$(echo ../.. | sed 's/[.]/\\&/g')"
++ echo ../..
++ sed 's/[.]/\\&/g'
+ printf '%s\n' '\.\./\.\.'
\.\./\.\.

也可以看看为什么 $(...) 优于...(反引号)?


在 bash 版本 4.4.20 中,命令替换的不带引号和带引号的版本$(...)给出相同的结果:

$ echo $BASH_VERSION
4.4.20(1)-release

$ set -x

$ printf "%s\n"  $(echo ../.. | sed 's/[.]/\\&/g')
++ echo ../..
++ sed 's/[.]/\\&/g'
+ printf '%s\n' '\.\./\.\.'
\.\./\.\.

相关内容