究竟为什么 result=$($shell_command) 会失败?
Exactly why does result=$($shell_command) fail?
为什么分配命令输出在某些情况下有效而在其他情况下似乎无效?我创建了一个最小的脚本来展示我的意思,我 运行 将它放在一个目录中,其中包含另一个文件 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"
让我们看看它究竟做了什么:
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计费数据的价值,因为缓冲区溢出已将 运行dom 垃圾放入一个应被删除的文件的名称中)。阅读常见问题解答,遵循其中包含的做法。
为什么分配命令输出在某些情况下有效而在其他情况下似乎无效?我创建了一个最小的脚本来展示我的意思,我 运行 将它放在一个目录中,其中包含另一个文件 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"
让我们看看它究竟做了什么:
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计费数据的价值,因为缓冲区溢出已将 运行dom 垃圾放入一个应被删除的文件的名称中)。阅读常见问题解答,遵循其中包含的做法。