在Unix shell脚本中设置环境variables的简明方法是什么?

我有几个Unix shell脚本,在开始做某些事情之前,我需要检查是否设置了某些环境variables,所以我这样做:

if [ -z "$STATE" ]; then echo "Need to set STATE" exit 1 fi if [ -z "$DEST" ]; then echo "Need to set DEST" exit 1 fi 

这是很多打字。 是否有一个更优雅的习惯用于检查是否设置了一组环境variables?

编辑:我应该提到,这些variables没有有意义的默认值 – 脚本应该错误,如果有任何未设置。

参数扩展

显而易见的答案是使用参数扩展的特殊forms之一:

 : ${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 =“”(空string)是可以的 – 不完全是你想要的,而是替代和旧的表示法。

第二个变体(使用:? )要求设置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} 

如果为空或未设置,则显示错误。 如果参数为空或未设置,则将单词(如果不存在单词的情况下的消息扩展)写入标准错误,并且shell(如果不是交互式)将退出。 否则,参数的值被replace。

冒号命令

我应该补充一点,冒号命令只是简单地评估它的参数,然后成功。 它是原来的shell注释符号(在' # '之前到行尾)。 很长一段时间,Bourne shell脚本都以冒号作为第一个字符。 C Shell将读取一个脚本并使用第一个字符来确定它是用于C Shell(“ # ”散列)还是Bourne shell(“ : ”冒号)。 然后内核join了这个行为,并join了对#!/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. 

在这种情况下,我应该怎么做?

简短的回答是“按照shellcheckbuild议”:

 : "${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的值如何扩展到第一个* ,然后是整个expression式不是双引号时的文件名列表。 这是shellcheck推荐的应该修复的。 我没有证实它不反对expression式用双引号引起来的forms,但是这是合理的假设。

尝试这个:

 [ -z "$STATE" ] && echo "Need to set STATE" && exit 1; 

你的问题依赖于你正在使用的shell。

Bourne shell在你之后留下的东西很less。

但…

它确实有效,几乎到处都是。

试试远离csh。 比起Bourne贝壳来说,它增加了花里胡哨的好处,但它现在真的很吱吱作响。 如果你不相信我,试试把cST中的STDERR分开! ( – :

这里有两种可能性。 上面的例子,即使用:

 ${MyVariable:=SomeDefault} 

你第一次需要引用$ MyVariable。 这需要env。 var MyVariable,如果当前未设置,则将SomeDefault的值分配给该variables供以后使用。

你也有可能:

 ${MyVariable:-SomeDefault} 

它只是将SomeDefaultreplace为使用此构造的variables。 它不会将值SomeDefault赋值给variables,并且在遇到此语句后,MyVariable的值仍然为空。

当然最简单的方法是将-u开关添加到shebang(脚本顶部的行),假设你正在使用bash

#!/bin/sh -u

这将导致脚本退出,如果任何未绑定的variables潜伏在内。

 ${MyVariable:=SomeDefault} 

如果MyVariable设置为非空,它将重置variables值(=没有任何反应)。
否则, MyVariable被设置为SomeDefault

在我看来,#!/ bin / sh最简单和最兼容的检查是:

 if [ "$MYVAR" = "" ] then echo "Does not exist" else echo "Exists" fi 

再次,这是/ bin / sh,并且在旧的Solaris系统上也是兼容的。

我一直使用:

 if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi 

恐怕不是那么简洁。

在CSH下你有$?STATE。

bash 4.2引入了-v运算符,用于testing名称是否设置为任何值,即使是空string。

 $ unset a $ b= $ c= $ [[ -va ]] && echo "a is set" $ [[ -vb ]] && echo "b is set" b is set $ [[ -vc ]] && echo "c is set" c is set 

上述解决scheme中没有一个适用于我的目的,部分原因是我在开始漫长的过程之前在环境中检查需要设置的variables的开放式列表。 我结束了这个:

 mapfile -t arr < variables.txt EXITCODE=0 for i in "${arr[@]}" do ISSET=$(env | grep ^${i}= | wc -l) if [ "${ISSET}" = "0" ]; then EXITCODE=-1 echo "ENV variable $i is required." fi done exit ${EXITCODE} 

我们可以写出一个很好的断言来同时检查一堆variables:

 # # assert if variables are set (to a non-empty string) # if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise # # Usage: assert_not_null [-f] variable ... # function assert_not_null() { local fatal var num_null=0 [[ "$1" = "-f" ]] && { shift; fatal=1; } for var in "$@"; do [[ -z "${!var}" ]] && printf '%s\n' "Variable '$var' not set" >&2 && ((num_null++)) done if ((num_null > 0)); then [[ "$fatal" ]] && exit 1 return 1 fi return 0 } 

示例调用:

 one=1 two=2 assert_not_null one two echo test 1: return_code=$? assert_not_null one two three echo test 2: return_code=$? assert_not_null -f one two three echo test 3: return_code=$? # this code shouldn't execute 

输出:

 test 1: return_code=0 Variable 'three' not set test 2: return_code=1 Variable 'three' not set 

对于像我这样的将来的人来说,我想要向前走一步,并参数化var名称,所以我可以遍历一个variables大小的variables名称列表:

 #!/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 

我倾向于加载我的loginshell中的函数,而不是使用外部shell脚本。 我使用类似这样的辅助函数来检查环境variables而不是任何设置variables:

 is_this_an_env_variable () local var="$1" if env |grep -q "^$var"; then return 0 else return 1 fi } 

这也可以是一个方法:

 if (set -u; : $HOME) 2> /dev/null ... ... 

http://unstableme.blogspot.com/2007/02/checks-whether-envvar-is-set-or-not.html

$? 语法很整齐:

 if [ $?BLAH == 1 ]; then echo "Exists"; else echo "Does not exist"; fi