我有一些Unix shell脚本,我需要在开始做之前检查某些环境变量是否已设置,所以我做了这样的事情:
if [ -z "$STATE" ]; then echo "Need to set STATE" exit 1 fi if [ -z "$DEST" ]; then echo "Need to set DEST" exit 1 fi
这是很多打字.是否有更优雅的习惯用于检查是否设置了一组环境变量?
编辑:我应该提到这些变量没有有意义的默认值 - 如果有任何未设置,脚本应该出错.
显而易见的答案是使用参数扩展的一种特殊形式:
: ${STATE?"Need to set STATE"} : ${DEST:?"Need to set DEST non-empty"}
或者,更好(请参阅下面的"双引号位置"部分):
: "${STATE?Need to set STATE}" : "${DEST:?Need to set DEST non-empty}"
第一个变体(仅使用?
)需要设置STATE,但是STATE =""(空字符串)是正确的 - 不完全是你想要的,而是替代和旧的表示法.
第二个变体(使用:?
)需要设置DEST并且非空.
如果您不提供任何消息,则shell会提供默认消息.
该${var?}
构造可移植回到版本7 UNIX和Bourne Shell(1978或其左右).这个${var:?}
构造稍微更近了:我认为它大概是1981年的System III UNIX,但在此之前可能已经在PWB UNIX中了.因此它位于Korn Shell和POSIX shell中,特别包括Bash.
它通常记录在shell的手册页中的一个名为Parameter Expansion的部分中.例如,bash
手册说:
${parameter:?word}如果为空或未设置则显示错误.如果参数为null或未设置,则单词的扩展(或者如果单词不存在则为该效果的消息)将写入标准错误,并且如果shell不是交互式,则退出.否则,参数的值将被替换.
我应该补充一点,冒号命令只是对其参数进行评估然后成功.它是原始的shell注释符号(在#
'到行尾之前).很长一段时间,Bourne shell脚本都有一个冒号作为第一个字符.C Shell将读取一个脚本并使用第一个字符来确定它是用于C Shell(一个#
'散列)还是Bourne shell(一个:
'冒号).然后内核加入了该行为并添加了对' #!/path/to/program
'和Bourne shell得到#
'评论的支持,并且冒号大会走到了路边.但是如果你遇到一个以冒号开头的脚本,现在你就知道为什么了.
blong在评论中问道:
对此讨论有何看法?https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749
讨论的要点是:
...但是,当我
shellcheck
(版本0.4.1),我收到此消息:In script.sh line 13: : ${FOO:?"The environment variable 'FOO' must be set and non-empty"} ^-- SC2086: Double quote to prevent globbing and word splitting.关于在这种情况下我应该做什么的任何建议?
简短的回答是"按照shellcheck
建议做":
: "${STATE?Need to set STATE}" : "${DEST:?Need to set DEST non-empty}"
为了说明原因,请研究以下内容.请注意,该:
命令不会回显其参数(但shell会评估参数).我们想看看参数,所以下面的代码用来printf "%s\n"
代替:
.
$ mkdir junk $ cd junk $ > abc $ > def $ > ghi $ $ x="*" $ printf "%s\n" ${x:?You must set x} # Careless; not recommended abc def ghi $ unset x $ printf "%s\n" ${x:?You must set x} # Careless; not recommended bash: x: You must set x $ printf "%s\n" "${x:?You must set x}" # Careful: should be used bash: x: You must set x $ x="*" $ printf "%s\n" "${x:?You must set x}" # Careful: should be used * $ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough abc def ghi $ x= $ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough bash: x: You must set x $ unset x $ printf "%s\n" ${x:?"You must set x"} # Not quite careful enough bash: x: You must set x $
请注意当整个表达式不是双引号时,值$x
是如何扩展到第一个*
然后是文件名列表.这是shellcheck
推荐应该修复的内容.我没有验证它不反对表达式用双引号括起来的形式,但是它是合理的假设它会没问题.
试试这个:
[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
您的问题取决于您正在使用的shell.
伯恩贝壳对你所追求的东西的影响很小.
但...
它确实有效,几乎无处不在.
试着远离csh.与Bourne外壳相比,它增加了它所添加的花里胡哨,但它现在真的吱吱作响.如果您不相信我,请尝试在csh中分离出STDERR!( - :
这里有两种可能性.上面的例子,即使用:
${MyVariable:=SomeDefault}
这是你第一次需要引用$ MyVariable.这需要环境.var MyVariable,如果当前未设置,则将SomeDefault的值赋给变量供以后使用.
您还有可能:
${MyVariable:-SomeDefault}
它只是将SomeDefault替换为您使用此构造的变量.它不会将值SomeDefault赋值给变量,并且在遇到此语句后,MyVariable的值仍为null.
当然最简单的方法是将-u
开关添加到shebang(脚本顶部的行),假设您正在使用bash
:
#!/bin/sh -u
如果任何未绑定的变量潜伏在内,这将导致脚本退出.
${MyVariable:=SomeDefault}
如果MyVariable
设置且不为null,则它将重置变量值(=没有任何反应).
否则,MyVariable
将设置为SomeDefault
.
以上将尝试执行${MyVariable}
,所以如果你只想设置变量do:
MyVariable=${MyVariable:=SomeDefault}
在我看来,#!/ bin/sh的最简单和最兼容的检查是:
if [ "$MYVAR" = "" ] then echo "Does not exist" else echo "Exists" fi
同样,这是针对/ bin/sh的,并且在旧的Solaris系统上也兼容.
bash
4.2介绍了-v
运算符,该运算符测试名称是否设置为任何值,甚至是空字符串.
$ unset a $ b= $ c= $ [[ -v a ]] && echo "a is set" $ [[ -v b ]] && echo "b is set" b is set $ [[ -v c ]] && echo "c is set" c is set
我一直用:
if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi
我恐怕没那么简洁了.
在CSH下你有$?STATE.
对于像我这样的未来人,我想更进一步,并参数化var名称,因此我可以遍历一个可变大小的变量名称列表:
#!/bin/bash declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN) for var_name in "${vars[@]}" do if [ -z "$(eval "echo \$$var_name")" ]; then echo "Missing environment variable $var_name" exit 1 fi done