总结

总结

我使用了以下之一

echo $(stuff)
echo `stuff`

stuff其中例如pwddate或更复杂的东西)。

然后我被告知这种语法是错误的、不好的做法、不优雅的、过度的、多余的、过于复杂的、货物崇拜编程、菜鸟的、幼稚的等等。

但是命令那么它到底出了什么问题呢?

答案1

总结

鞋底stuff最可能为您工作。


完整答案

会发生什么

当你运行时foo $(stuff),会发生以下情况:

  1. stuff在子 shell 中执行,设置需要一些时间;
  2. 它的输出(stdout)不是被打印出来,而是$(stuff)在参数中替换foo;Stderr 仍然转到 /dev/stderr。
  3. 执行 生成stuff并被 捕获的字符串$(...),例如不是引用,可能会发生全局和分裂。
  4. 默认情况下,第一次拆分将在 IFS 中的任意字符处中断(拆分)字符串<space><tab><newline>
  5. 然后进行通配符处理,其中 any*?valid[]将被替换为列表匹配的文件。
  6. 然后foo运行,它的命令行参数显然取决于stuff返回的内容。

这种$(…)机制称为“命令替换”。 在您的例子中,主命令是,echo它基本上将其命令行参数打印到 stdout,并以单个空格分隔。 因此,任何stuff试图打印到 stdout 的内容都会被捕获、修改、扩展并提供给echo,然后由 打印到 stdout echo

如果您希望将的输出stuff打印到标准输出,只需运行唯一的stuff

该语法与(同名:“命令替换”)的`…`用途相同,但存在一些差异,因此您不能盲目地互换它们。请参阅$(…)此常见问题解答这个问题


我应该echo $(stuff)不顾一切地避免吗?

如果您知道自己在做什么,那么您可能想使用某个理由。如果您不知道自己在做什么,那么echo $(stuff)您应该避免使用同一个理由。echo $(stuff)

重点是stuffecho $(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,最好还是保持这种方式。需要考虑的事项:echoecho "$var"

  1. 运行var=$(stuff) stuff一次;即使命令很快,避免计算两次相同的输出是正确的做法。或者stuff除了写入 stdout 之外,还可能有其他效果(例如创建临时文件、启动服务、启动虚拟机、通知远程服务器),因此您不想多次运行它。
  2. 如果stuff生成的输出与时间相关或有些随机,您可能会从foo "$(stuff)"和获得不一致的结果bar "$(stuff)"。在var=$(stuff)的值$var固定后,您可以确定foo "$var"bar "$var"获得相同的命令行参数。

在某些情况下,foo "$var"你可能想要(需要)使用foo $var,特别是如果stuff生成多种的的参数foo(如果您的 shell 支持,数组变量可能更好)。再次强调,要知道自己在做什么。说到 和之间echo的区别,与和之间的区别相同。echo $varecho "$var"echo $(stuff)echo "$(stuff)"


*相等的(有点)?

我说的echo "$(stuff)"是 等价于stuff。至少有两个问题使得它不完全等价:

  1. $(stuff)在子 shell 中运行stuff,因此最好说echo "$(stuff)"相当于(stuff)。如果命令在子 shell 中运行,则会影响它们在其中运行的 shell,而不会影响主 shell。

    在这个例子中stuffa=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)版本。

  2. echo不是一个显示不受控制的数据的好工具echo "$var"我们讨论的应该是printf '%s\n' "$var"。但是由于问题提到了,而且最可能的解决方案是首先echo不使用,所以我决定到现在为止不介绍。echoprintf

  3. stuff或者(stuff)将交错 stdout 和 stderr 输出,而echo $(stuff)将打印来自(首先运行)的所有 stderr 输出stuff,然后才打印被echo(最后运行)消化的 stdout 输出。

  4. $(…)删除所有尾随换行符,然后echo将其添加回去。因此echo "$(printf %s 'a')" | xxd输出结果与 不同printf %s 'a' | xxd

  5. 某些命令(ls例如)的工作方式取决于标准输出是否为控制台;所以ls | cat不一样ls。同样地,echo $(ls)工作方式也不同ls

    撇开ls不谈,在一般情况下如果你必须强制这种其他行为,这stuff | cat比它更好,echo $(stuff)或者echo "$(stuff)"因为它不会触发这里提到的所有其他问题。

  6. 可能有不同的退出状态(为了这个 wiki 答案的完整性而提到;有关详细信息,请参阅另一个答案值得称赞。

答案2

另一个区别:子 shell 的退出代码丢失,因此echo改为检索的退出代码。

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0

相关内容