究竟为什么result = $($ shell_command)失败?

时间:2015-07-31 23:33:10

标签: bash unix find

为什么分配命令输出在某些情况下有效,而在其他情况下却看不到?我创建了一个最小的脚本来显示我的意思,并在一个目录中运行它,其中包含另一个文件a.txt。请看???在下面的脚本中,让我知道什么是错的,也许尝试一下。感谢。

#!/bin/bash

## setup so anyone can copy/paste/run this script ("complete" part of MCVE)
tempdir=$(mktemp -d "${TMPDIR:-/tmp}"/demo.XXXX) || exit # make a temporary directory
trap 'rm -rf "$tempdir"' 0 # delete temporary directory on exit
cd "$tempdir" || exit      # don't risk changing non-temporary directories
touch a.txt                # create a sample file

cmd1="find . -name 'a*' -print"
eval $cmd1         # this produces "./a.txt" as expected
res1=$($cmd1)
echo "res1=$res1"  # ??? THIS PRODUCES ONLY "res1=" , $res1 is blank  ???

# let's try this as a comparison

cmd2="ls a*"
res2=$($cmd2)
echo "res2=$res2"  # this produces "res2=a.txt"

1 个答案:

答案 0 :(得分:3)

让我们看看它到底发生了什么:

cmd1="find . -name 'a*' -print"
res1=$($cmd1)
echo "res1=$res1"  # ??? THIS PRODUCES ONLY "res1=" , $res1 is blank  ???

根据BashFAQ #50,执行res1=$($cmd1)会执行以下操作,假设您没有名称以'a开头且以'结尾的文件(是的,带单引号)作为名称的一部分),并且您尚未启用nullglob shell选项:

res1=$( find . -name "'a*'" -print )

注意引用而不是名字?引用表示'被视为数据,而不是语法;因此,不是对*是否被扩展产生任何影响,它们只是一个额外的元素,需要分配任何文件名才能匹配,这就是为什么你得到一个没有匹配的结果的原因。相反,正如常见问题解答告诉你的那样,使用一个函数:

cmd1() {
  find . -name 'a*' -print
}
res1=$(cmd1)

......或数组:

cmd1=( find . -name 'a*' -print )
res1=$( "${cmd1[@]}" )

现在,为什么会发生这种情况?阅读常见问题解答以获得完整说明。简而言之:参数扩展在语法引用已经应用之后发生。从安全角度来看,这实际上是一件非常好的事情 - 如果所有扩展都是通过完全解析递归进行的,那么就不可能写出安全的处理恶意数据的bash中的代码。

现在,如果您不关心安全性,并且不关心最佳做法,那么不要'关心能够使用不寻常的文件名正确解释结果:

cmd1="find . -name 'a*' -print"
res1=$(eval "$cmd1") # Force parsing process to restart from beginning. DANGEROUS if cmd1
                     # is not static (ie. constructed with user input or filenames);
                     # prone to being used for shell injection attacks.
echo "res1=$res1"

......但不要这样做。 (只有在一个人无法做到这一点之后,人们才能摆脱草率的做法;而对于那些以前的工作中的系统管理员工作人员来说,这一点在备份维护时出现了脚本删除了几个TB值的计费数据,因为缓冲区溢出已将随机垃圾放入应删除的文件名中。阅读常见问题解答,按照其中包含的惯例进行操作。