我正在尝试在脚本继续其主要作业之前检查我的脚本所依赖的所有非POSIX命令是否存在。这将帮助我确保我的脚本稍后不会因缺少命令而生成错误。
我希望将所有此类非POSIX命令的列表保存在名为DEPS
的变量中,以便随着脚本的发展并依赖于更多命令,我可以编辑此变量。
我希望脚本支持带有空格的命令,例如my program
。
这是我的剧本。
#!/bin/sh
DEPS='ssh scp "my program" sftp'
for i in $DEPS
do
echo "Checking $i ..."
if ! command -v "$i"
then
echo "Error: $i not found"
else
echo "Success: $i found"
fi
echo
done
但是,这不起作用,因为"my program"
在for
循环迭代时分为两个单词:"my
和program"
,如您所见下面的输出。
# sh foo.sh
Checking ssh ...
/usr/bin/ssh
Success: ssh found
Checking scp ...
/usr/bin/scp
Success: scp found
Checking "my ...
Error: "my not found
Checking program" ...
Error: program" not found
Checking sftp ...
/usr/bin/sftp
Success: sftp found
我预期的输出是:
# sh foo.sh
Checking ssh ...
/usr/bin/ssh
Success: ssh found
Checking scp ...
/usr/bin/scp
Success: scp found
Checking my program ...
Error: my program not found
Checking sftp ...
/usr/bin/sftp
Success: sftp found
如何在保持脚本POSIX兼容的同时解决此问题?
答案 0 :(得分:2)
我将重复我之前提出的问题的答案:使用带有here文档而不是for循环的while循环。您可以在字符串中嵌入换行符,如果这些命令名称可能包含空格,则只需在字符串中分隔命令名称即可。 (如果您的命令名称包含换行符,请强烈考虑重命名它们。)
为了获得最大的POSIX兼容性,请使用printf
,因为echo
的POSIX规范非常宽松,因为在定义标准之前各种shell中echo
的实现方式不同。
deps="ssh
scp
my program
sftp
"
while read -r cmd; do
printf "Checking $cmd ...\n"
if ! command -v "$cmd"; then
printf "Error: $i not found\n"
else
printf "Success: $cmd found\n"
fi
printf "\n"
done <<EOF
$deps
EOF
答案 1 :(得分:1)
这是因为参数扩展后的步骤是字符串拆分和glob-expansion - 不语法级解析(例如处理引用)。要一直回到解析过程的开头,您需要使用eval
。
坦率地说,最好的方法是:
......正确的阵列支持在现代贝壳中无处不在;明确地编写正确的代码,特别是在处理不受信任的数据时,如果没有它就会更难。
也就是说,您可以选择使用$@
来存储您的内容,使用eval
可以设置,尽管是危险的:
deps='goodbye "cruel world"'
eval "set -- $deps"
for program; do
echo "processing $program"
done
如果在函数内部执行此操作,则只会覆盖函数的参数列表,而不会修改全局列表。
或者,eval "yourfunction $deps"
将具有相同的效果,将函数中的参数列表设置为运行$deps
内容的所有常规解析和扩展阶段的结果。
答案 2 :(得分:1)
由于脚本在您的控制中,您可以合理安全地使用eval
,因此@Charles Duffy的答案是一个简单而好的解决方案。用它。 :)
另外,请考虑使用autoconf
来生成通常的configure
脚本,这样可以满足您的需求 - 例如检查命令等等......至少,检查一些配置脚本,了解如何解决常见问题...
如果你想玩自己的实现:
sed
,cat
cp
等。这些程序的名称中不包含空格,也不包含$ PATH。for
循环用于空格分隔元素,除非您将它们作为函数参数 - 因此您可以使用"$@"
由于起始脚本可能如下所示:
_check_core_deps() {
for _cmd
do
_cpath=$(command -v "$_cmd")
case "$_cpath" in
/*) continue;;
*) echo "Missing install dependency [$_cmd] - can't continue" ; exit 1 ;;
esac
done
return 0
}
core_deps="grep sed hooloovoo cp" #list of "core" commands - they doesn't contains spaces
_check_core_deps $core_deps || exit 1
以上内容会炸毁不存在的“hooloovoo”命令。 :)
现在您可以安全地继续,安装脚本所需的所有核心命令都可用。在下一步中,您可以检查其他奇怪的依赖项。
一些想法:
# function what returns your dependecies as lines from HEREDOC
# (e.g. could contain any character except "\n")
# you can decorate the dependecies with comments...
# because we have sed (checked in the 1st step, can use it)
# if want, you can add "fields" too, for some extended functinality with an specified delimiter
list_deps() {
_sptab=$(printf " \t") # the $' \t' is approved by POSIX for the next version only
#the "sed" removes comments and empty lines
#the UUOC (useless use of cat) is intentional here
#for example if you want add "tr" before the "sed"
#of course, you can remove it...
cat - <<DEPS |sed "s/[$_sptab]*#.*//;/^[$_sptab]*$/d"
########## DEPENDECIES ############
#some comment
ssh
scp
sftp
#comment
#bla bla
my program #some comment
/Applications/Some Long And Spaced OSX Apllication.app
DEPS
########## END of DEPENDECIES #####
}
_check_deps() {
#in the "while" loop you can use IFS=: or such and adding anouter variable to read
#for getting more fields for some extended functionality
list_deps | while read -r line
do
#do any checks with the line
#implement additional functionalities as functions
#etc...
#remember - your in an subshell here
printf "command:%s\n" "$line"
done
}
_check_deps
还有一件事:),(或两件事)
echo
。 POSIX未定义包含转义字符时的行为方式(例如echo "some\nwed"
)。使用:printf '%s' "$variable"