命令替换中的 bash 变量替换
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/13290186/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
bash variable substitution within command substitution
提问by Wei Li
I want to do something like the following:
我想做如下事情:
#!/bin/bash
cmd="find . -name '*.sh'"
echo $($cmd)
What I expect is that it will show all the shell script files in the current directory, but nothing happened.
我期望的是它会显示当前目录中的所有shell脚本文件,但什么也没发生。
I know that I can solve the problem with eval according to this post
我知道我可以根据这篇文章使用 eval 解决问题
#!/bin/bash
cmd="find . -name '*.sh'"
eval $cmd
So my question is why command substitution doesn't work here and what's the difference between $(...) and eval in terms of the question?
所以我的问题是为什么命令替换在这里不起作用以及 $(...) 和 eval 在问题方面有什么区别?
采纳答案by Tomek Wyderka
Command substitution works here. Just you have wrong quoting. Your script find only one file name! This one with single quotes and asteriks in it:
命令替换在这里起作用。只是你引用错误。您的脚本只找到一个文件名!这个带有单引号和星号的:
'*.sh'
You can create such not usual file by this command and test it:
您可以通过此命令创建这种不常用的文件并对其进行测试:
touch "'*.sh'"
Quoting in bash is different than in other programming languages. Check out details in this article: White Shell
bash 中的引用与其他编程语言中的不同。查看本文中的详细信息: 白壳
What you need is this quoting:
你需要的是这样的引用:
cmd="find . -name *.sh"
echo $($cmd)
回答by chepner
Since you are already including the patter *.shinside double quotes, there's no need for the single quotes to protect the pattern, and as a result the single quotes are partof the pattern. 
由于您已经将模式包含*.sh在双引号内,因此不需要单引号来保护模式,因此单引号是模式的一部分。
You can try using an array to keep *.shquoted until it is passed to the command substitution:
您可以尝试使用数组来保持*.sh引用,直到它被传递给命令替换:
cmd=(find . -name '*.sh')
echo $("${cmd[@]}")
I don't know if there is a simple way to convert your original string to an array without the pattern being expanded.
我不知道是否有一种简单的方法可以将原始字符串转换为数组而不扩展模式。
Update: This isn't toobad, but it's probably better to just create the array directly if you can.
更新:这是不是太糟糕,但它可能会更好,只是直接,如果你可以创建数组。
cmd="find . -name *.sh"
set -f
cmd=($cmd)
set +f
echo $("${cmd[@]}")
回答by platforms
When you use the echo $($cmd)syntax, it's basically equivalent to just putting $cmdon it's own line. The problem is the way bash wants to interpolate the wildcard before the command runs. The way to protect against that is to put the variable containing the *char in quotes AGAIN when you dereference them in the script.
当您使用echo $($cmd)语法时,它基本上等同于只是放在$cmd自己的行上。问题是 bash 想要在命令运行之前插入通配符的方式。防止这种情况的方法是*在脚本中取消引用它们时,将包含字符的变量再次放在引号中。
But if you put the whole command find . -name "*.sh"in a variable, then quote it with `echo $("$cmd"), the shell will interpret that to mean that the entire line is a file to execute, and you get a file not found error.
但是如果你把整个命令find . -name "*.sh"放在一个变量中,然后用 `echo $("$cmd") 引用它,shell 会解释这意味着整行是一个要执行的文件,你会得到一个文件未找到错误.
So it really depends on what you really need in the variable and what can be pulled out of it. If you need the program in the variable, this will work:
因此,这实际上取决于您在变量中真正需要什么以及可以从中提取什么。如果您需要变量中的程序,这将起作用:
#!/bin/bash
cmd='/usr/bin/find'
$cmd . -name "*.sh" -maxdepth 1
This will find all the files in the current working directory that end in .shwithout having the shell interpolate the wildcard.
这将找到当前工作目录中以 结尾的所有文件,.sh而无需 shell 插入通配符。
If you need the pattern to be in a variable, you can use:
如果您需要将模式放在变量中,您可以使用:
#!/bin/bash
pattern="*.sh"
/usr/bin/find . -name "$pattern" -maxdepth 1
But if you put the whole thing in a variable, you won't get what you expect. Hope this helps. If a bash guru knows something I'm missing I'd love to hear it.
但是如果你把整个事情放在一个变量中,你就不会得到你所期望的。希望这可以帮助。如果 bash 大师知道我遗漏了什么,我很乐意听到。

