“echo $(stuff)”或“echo `stuff`”有什么问题?

Kam*_*ski 35 sh shell-script echo

我使用了以下之一

echo $(stuff)
echo `stuff`
Run Code Online (Sandbox Code Playgroud)

stuff例如pwddate或更复杂的东西在哪里)。

然后我被告知这种语法是错误的,一种不好的做法,不优雅,过度,多余,过于复杂,货物崇拜编程,noobish,天真等等。

但是该命令确实有效,那么它到底有什么问题呢?

Kam*_*ski 67

tl;博士

鞋底stuff可能对你有用。


完整答案

发生什么了

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

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

这种$(…)机制称为“命令替换”。在您的情况下,主命令echo基本上将其命令行参数打印到由单个空格分隔的标准输出。因此,任何stuff尝试打印到标准输出的内容都会被捕获、修改、扩展并提供给echo,然后通过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")'
Run Code Online (Sandbox Code Playgroud)

对于通配:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'
Run Code Online (Sandbox Code Playgroud)

如您所见echo "$(stuff)",相当于(-ish*) 到stuff. 您可以使用它,但是以这种方式使事情复杂化有什么意义呢?

另一方面,如果您希望的输出stuff进行拆分+通配,那么您可能会发现echo $(stuff)很有用。不过,这必须是你有意识的决定。

有一些生成输出的命令应该被评估(包括拆分、通配等)并由 shell 运行,所以eval "$(stuff)"是一种可能性(请参阅此答案)。我从未见过需要其输出在打印之前进行额外拆分+通配的命令。故意使用echo $(stuff)似乎非常罕见。


怎么样var=$(stuff); echo "$var"

好点子。这个片段:

var=$(stuff)
echo "$var"
Run Code Online (Sandbox Code Playgroud)

应该等价于echo "$(stuff)"(-ish*)到stuff. 如果是整个代码,只需运行即可stuff

但是,如果您需要stuff多次使用输出,则此方法

var=$(stuff)
foo "$var"
bar "$var"
Run Code Online (Sandbox Code Playgroud)

通常比

foo "$(stuff)"
bar "$(stuff)"
Run Code Online (Sandbox Code Playgroud)

即使fooecho并且您进入echo "$var"了您的代码,最好保持这种方式。需要考虑的事项:

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

在某些情况下,foo "$var"您可能希望(需要)使用foo $var,而不是使用,尤其是在为(如果您的 shell 支持数组变量可能会更好)stuff生成多个参数时foo。再次,知道你在做什么。当谈到echo的区别echo $varecho "$var"相同之间echo $(stuff)echo "$(stuff)"


*等效(-ish)?

我说echo "$(stuff)"相当于(-ish)到stuff. 至少有两个问题使它不完全等效:

  1. $(stuff)stuff在子shell中运行,所以最好说echo "$(stuff)"是等价的(-ish)到(stuff). 影响它们运行的​​ shell 的命令,如果在子 shell 中,不会影响主 shell。

    在这个例子中stuffa=1; echo "$a"

     a=0
     echo "$(a=1; echo "$a")"      # echo "$(stuff)"
     echo "$a"
    
    Run Code Online (Sandbox Code Playgroud)

    比较一下

     a=0
     a=1; echo "$a"                # stuff
     echo "$a"
    
    Run Code Online (Sandbox Code Playgroud)

     a=0
     (a=1; echo "$a")              # (stuff)
     echo "$a"
    
    Run Code Online (Sandbox Code Playgroud)

    再比如,开始stuffcd /; pwd

     cd /bin
     echo "$(cd /; pwd)"           # echo "$(stuff)"
     pwd
    
    Run Code Online (Sandbox Code Playgroud)

    和测试stuff(stuff)版本。

  2. echo不是显示不受控制的数据的好工具。这echo "$var"我们谈论应该是printf '%s\n' "$var"。但是既然提了这个问题echo,而且最有可能的解决方案是一开始就不使用echo,所以我决定暂时不做介绍printf

  3. stuffor(stuff)将交错 stdout 和 stderr 输出,同时echo $(stuff)将打印stuff(首先运行)的所有 stderr 输出,然后才打印由(最后运行)消化的 stdout 输出echo

  4. $(…)去掉任何尾随的换行符,然后echo将其添加回来。所以echo "$(printf %s 'a')" | xxd给出不同的输出printf %s 'a' | xxd

  5. ls根据标准输出是否是控制台,某些命令(例如)的工作方式有所不同;所以ls | cat不一样ls。同样的echo $(ls)工作方式与ls.

    ls放在一边,在一般情况下,如果你非要逼那么这等行为stuff | cat是优于echo $(ls)echo "$(ls)"因为它不引发其它的所有问题,这里提到。

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

  • 还有一个区别:`stuff` 方式将交错`stdout` 和`stderr` 输出,而`echo `stuff` ` 将打印`stuff` 的所有`stderr` 输出,然后才打印`stdout` 输出. (6认同)
  • 这是另一个示例:bash 脚本中的引号几乎不会太多。如果你不想明确地进行通配和扩展,`echo $(stuff)` 会让你陷入更多的麻烦,因为`echo "$(stuff)"`。 (3认同)
  • 还有一个细微的区别:`$(...)` 去掉任何尾随的换行符,然后 `echo` 把它加回来。所以`echo "$(printf %s 'a')" | xxd` 给出与 `printf %s 'a' 不同的输出 | xxd`。 (2认同)

Waf*_*fle 6

另一个区别:子shell退出代码丢失,因此echo取而代之的是退出代码。

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
Run Code Online (Sandbox Code Playgroud)

  • 我相信这表明返回码`$?` 将是`echo`(对于`echo $(stuff)`)的返回码,而不是由`stuff` 返回的返回码。`stuff` 函数 `return 1`(退出代码),但是 `echo` 将其设置为“0”,而不管 `stuff` 的返回代码如何。仍然应该在答案中。 (4认同)
  • 欢迎使用超级用户!你能*解释*你所展示的区别吗?谢谢 (3认同)