我使用了以下之一
echo $(stuff)
echo `stuff`
(stuff
其中例如pwd
或date
或更复杂的东西)。
然后我被告知这种语法是错误的、不好的做法、不优雅的、过度的、多余的、过于复杂的、货物崇拜编程、菜鸟的、幼稚的等等。
但是命令做那么它到底出了什么问题呢?
答案1
总结
鞋底stuff
会最可能为您工作。
完整答案
会发生什么
当你运行时foo $(stuff)
,会发生以下情况:
stuff
在子 shell 中执行,设置需要一些时间;- 它的输出(stdout)不是被打印出来,而是
$(stuff)
在参数中替换foo
;Stderr 仍然转到 /dev/stderr。 - 执行 生成
stuff
并被 捕获的字符串$(...)
,例如不是引用,可能会发生全局和分裂。 - 默认情况下,第一次拆分将在 IFS 中的任意字符处中断(拆分)字符串
<space><tab><newline>
。 - 然后进行通配符处理,其中 any
*
和?
valid[]
将被替换为列表匹配的文件。 - 然后
foo
运行,它的命令行参数显然取决于stuff
返回的内容。
这种$(…)
机制称为“命令替换”。 在您的例子中,主命令是,echo
它基本上将其命令行参数打印到 stdout,并以单个空格分隔。 因此,任何stuff
试图打印到 stdout 的内容都会被捕获、修改、扩展并提供给echo
,然后由 打印到 stdout echo
。
如果您希望将的输出stuff
打印到标准输出,只需运行唯一的stuff
。
该语法与(同名:“命令替换”)的`…`
用途相同,但存在一些差异,因此您不能盲目地互换它们。请参阅$(…)
此常见问题解答和这个问题。
我应该echo $(stuff)
不顾一切地避免吗?
如果您知道自己在做什么,那么您可能想使用某个理由。如果您不知道自己在做什么,那么echo $(stuff)
您应该避免使用同一个理由。echo $(stuff)
重点是stuff
和echo $(stuff)
绝不是等价的。后者意味着在 的输出上调用 split+glob 运算符stuff
并使用 的默认值$IFS
。用双引号括住命令替换可以防止这种情况发生。用单引号括住命令替换使其不再是命令替换。
为了在分割时观察这一点,请运行以下命令:
echo "a b"
echo $(echo "a b")
echo "$(echo "a b")" # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a b")'
对于通配符:
echo "/*"
echo $(echo "/*")
echo "$(echo "/*")" # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'
如您所见,echo "$(stuff)"
它等效于stuff
。您可以使用它,但这样把事情复杂化有什么意义呢?
另一方面,如果你想的输出stuff
进行拆分+合并,然后你可能觉得echo $(stuff)
有用。但这必须由您有意识地决定。
有些命令会生成输出,这些输出应该由 shell 进行评估(包括拆分、匹配等)并运行,因此也eval "$(stuff)"
有可能(请参阅这个答案)我从未见过需要将其输出进行额外的拆分和匹配才能被打印. 故意使用echo $(stuff)
似乎很少见。
关于什么var=$(stuff); echo "$var"
?
很好的观点。以下是代码片段:
var=$(stuff)
echo "$var"
应该等同echo "$(stuff)"
于stuff
。如果是整个代码,则只需运行stuff
即可。
stuff
但是,如果你需要多次使用输出,那么这种方法
var=$(stuff)
foo "$var"
bar "$var"
通常比
foo "$(stuff)"
bar "$(stuff)"
即使您在代码中得到了foo
,最好还是保持这种方式。需要考虑的事项:echo
echo "$var"
- 运行
var=$(stuff)
stuff
一次;即使命令很快,避免计算两次相同的输出是正确的做法。或者stuff
除了写入 stdout 之外,还可能有其他效果(例如创建临时文件、启动服务、启动虚拟机、通知远程服务器),因此您不想多次运行它。 - 如果
stuff
生成的输出与时间相关或有些随机,您可能会从foo "$(stuff)"
和获得不一致的结果bar "$(stuff)"
。在var=$(stuff)
的值$var
固定后,您可以确定foo "$var"
和bar "$var"
获得相同的命令行参数。
在某些情况下,foo "$var"
你可能想要(需要)使用foo $var
,特别是如果stuff
生成多种的的参数foo
(如果您的 shell 支持,数组变量可能更好)。再次强调,要知道自己在做什么。说到 和之间echo
的区别,与和之间的区别相同。echo $var
echo "$var"
echo $(stuff)
echo "$(stuff)"
*相等的(有点)?
我说的echo "$(stuff)"
是 等价于stuff
。至少有两个问题使得它不完全等价:
$(stuff)
在子 shell 中运行stuff
,因此最好说echo "$(stuff)"
相当于(stuff)
。如果命令在子 shell 中运行,则会影响它们在其中运行的 shell,而不会影响主 shell。在这个例子中
stuff
是a=1; echo "$a"
:a=0 echo "$(a=1; echo "$a")" # echo "$(stuff)" echo "$a"
比较
a=0 a=1; echo "$a" # stuff echo "$a"
与
a=0 (a=1; echo "$a") # (stuff) echo "$a"
另一个例子,从
stuff
以下开始cd /; pwd
:cd /bin echo "$(cd /; pwd)" # echo "$(stuff)" pwd
以及测试
stuff
和(stuff)
版本。echo
不是一个显示不受控制的数据的好工具。echo "$var"
我们讨论的应该是printf '%s\n' "$var"
。但是由于问题提到了,而且最可能的解决方案是首先echo
不使用,所以我决定到现在为止不介绍。echo
printf
stuff
或者(stuff)
将交错 stdout 和 stderr 输出,而echo $(stuff)
将打印来自(首先运行)的所有 stderr 输出stuff
,然后才打印被echo
(最后运行)消化的 stdout 输出。$(…)
删除所有尾随换行符,然后echo
将其添加回去。因此echo "$(printf %s 'a')" | xxd
输出结果与 不同printf %s 'a' | xxd
。某些命令(
ls
例如)的工作方式取决于标准输出是否为控制台;所以ls | cat
不一样ls
。同样地,echo $(ls)
工作方式也不同ls
。撇开
ls
不谈,在一般情况下如果你必须强制这种其他行为,这stuff | cat
比它更好,echo $(stuff)
或者echo "$(stuff)"
因为它不会触发这里提到的所有其他问题。可能有不同的退出状态(为了这个 wiki 答案的完整性而提到;有关详细信息,请参阅另一个答案值得称赞。
答案2
另一个区别:子 shell 的退出代码丢失,因此echo
改为检索的退出代码。
> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0