我有一系列目录,我需要运行各种shell命令,并且我已经制作了一个名为dodirs.sh的简短脚本,以简化在每个目录中运行命令的步骤:
#!/bin/bash
echo "Running in each directory: $@"
for d in ./*/; do
(
cd "$d"
pwd
eval "$@"
)
done
对于许多简单命令来说这很好,但有些有问题,例如:
grep "free energy TOTEN" OUTCAR | tail -1
在每个目录中的文件中查找字符串。
似乎管道和/或报价是麻烦,因为如果我说:
dodirs.sh grep "free energy TOTEN" OUTCAR
我得到一个合理的(如果是长期输出):
Running in each directory: grep free energy TOTEN OUTCAR
...
OUTCAR: free energy TOTEN = -888.53122906 eV
OUTCAR: free energy TOTEN = -888.53132396 eV
OUTCAR: free energy TOTEN = -888.531324 eV
...
我注意到echo的结果丢失了引号,所以这有点奇怪。另一方面,如果我说:
dodirs.sh grep "free energy TOTEN" OUTCAR | tail -1
然后我得到了荒谬的说法:
...
grep: energy: No such file or directory
grep: TOTEN: No such file or directory
...
请注意,现在回声根本没有回显,显然是错误解释了这条线。
我是否有某种方法可以转义字符,或将参数打包到我的dodirs.sh脚本中?
也许有人知道一个更好的方法?
答案 0 :(得分:0)
引号消失,因为一旦shell识别要作为参数传递给脚本的单词,它们就不再需要了。在您的脚本中,$1
为grep
,$2
为free energy TOTEN
等。
您确实需要转义管道(使用反斜杠\|
或引用'|'
),以便也作为参数传递给{{ 1}}。
eval
答案 1 :(得分:0)
考虑:
#!/bin/bash
# use printf %q to generate a command line identical to what we're actually doing
printf "Running in each directory: " >&2
printf '%q ' "$@" >&2
echo >&2
# use && -- we don't want to execute the command if cd into a given directory failed!
for d in ./*/; do
(cd "$d" && echo "$PWD" >&2 && "$@")
done
这更加可预测:它通过精确参数列表,因此对于一般命令,您可以自然地引用它。 (这与使用find -exec
或其他调用execv*
的工具完全相同 - 使用文字传递参数列表进行家庭调用;因此,这意味着您获得与{{{{{{{{ 1}},sudo
,chpst
,chroot
等。)
对于单个命令,调用看起来像您期望的那样:
setsid
要执行shell指令(例如管道),请显式执行shell:
dodirs grep "free energy TOTEN" OUTCAR
...或者,如果您愿意让调用者依赖于实现细节(例如,这是通过shell实现的,并且确切地 shell' s实现了),使用dodirs sh -c 'grep "free energy TOTEN" OUTCAR | tail -n 1'
# ^^ ^^
:
eval
这可能稍微多一些工作,但它使您符合标准的UNIX约定,并且如果调用者未能引用他们的参数dodirs eval 'grep "free energy TOTEN" OUTCAR | tail -n 1'
# ^^^^
- 安全,则可以避免冒着shell注入漏洞的风险。