我问为什么这三个命令给出三个不同的答案:
$ 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' '\.\./\.\.'
\.\./\.\.