在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.
在这种情况下,我应该怎么做?
简短的回答是“按照shellcheck
build议”:
: "${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