在命令替换中使用Bash变量替换

7

我想要做如下的事情:

#!/bin/bash

cmd="find . -name '*.sh'"
echo $($cmd)

我希望它会显示当前目录中所有的Shell脚本文件,但什么也没发生。
我知道可以通过eval根据这个post来解决问题。
#!/bin/bash

cmd="find . -name '*.sh'"
eval $cmd

所以我的问题是为什么命令替换在这里不起作用,$(...)和eval在这个问题上有什么区别?
3个回答

2
命令替换在这里可以使用。只是你引号使用错误了。你的脚本只会找到一个文件名!这个文件名带有单引号和星号:
'*.sh'

你可以使用以下命令来创建这样的不寻常文件并进行测试:

touch "'*.sh'"

在bash中,引用的方式与其他编程语言不同。请查看答案了解详细信息。

你需要使用以下引用:

cmd="find . -name *.sh"
echo $($cmd)

这是我最初发布的内容。问题在于模式在命令执行之前就被扩展了,因此你只能找到与模式匹配的文件(即你无法获取子目录中的任何文件)。 - chepner
chepner: 我现在看到你的答案包含了我的一部分。我被你建议的数组所误导,没有仔细阅读你的帖子。 - Tomek Wyderka

1

既然您已经在双引号内包含了模式*.sh,就没有必要使用单引号来保护该模式,因此单引号是模式的一部分。

您可以尝试使用数组来保持*.sh在传递给命令替换之前被引用:

cmd=(find . -name '*.sh')
echo $("${cmd[@]}")

我不知道是否有一种简单的方法可以将原始字符串转换为数组,而无需扩展模式。

更新:这并不是太糟糕,但如果可能的话,最好直接创建数组。

cmd="find . -name *.sh"
set -f
cmd=($cmd)
set +f
echo $("${cmd[@]}")

1
我其实喜欢这个数组技巧!如果你想将搜索限制在当前工作目录中,记得在 find 命令后加上 -maxdepth 1 - platforms
除非您使用其他谓词来细化搜索,否则给定的带有“-maxdepth 1”的查找命令将(我认为)等同于简单的模式匹配。 - chepner
好的,我没有使用 -maxdepth 1 运行它,结果它遍历了目录并在下面找到了更多的 shell 脚本。使用该选项后,它将搜索范围限制在当前工作目录中。 - platforms
这就是我所说的其他谓词。*.sh也会匹配当前目录中的所有shell脚本,但不允许您根据文件类型、访问时间、文件大小或其他find提供的属性进行过滤。 - chepner

0

当你使用echo $($cmd)语法时,它基本上等同于将$cmd放在自己的一行中。问题在于bash在运行命令之前想要插入通配符的方式。保护自己免受此类问题的方法是在脚本中引用包含*字符的变量时再次将其放入引号中。

但是,如果您将整个命令find . -name "*.sh"放入变量中,然后用`echo $("$cmd")`引用它,shell会将其解释为整行是要执行的文件,从而导致找不到文件的错误。

因此,这真的取决于您在变量中需要什么以及可以从中提取什么。如果您需要在变量中使用程序,则可以使用以下方法:

#!/bin/bash

cmd='/usr/bin/find'
$cmd . -name "*.sh" -maxdepth 1

这将在当前工作目录中查找所有以.sh结尾的文件,而不需要Shell插值通配符。

如果您需要将模式存储在变量中,可以使用以下方法:

#!/bin/bash

pattern="*.sh"
/usr/bin/find . -name "$pattern" -maxdepth 1

但是如果你把整个东西放在一个变量中,你得到的结果可能不是你期望的。希望这可以帮到你。如果有Bash大师知道我漏掉了什么,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接