有什么简单的方法来检查环境变量是否在Unix shell脚本中设置?

我有一些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

这需要大量的打字。是否有更优雅的习惯用法来检查是否设置了一组环境变量?

编辑:我应该提到的是,这些变量没有任何有意义的默认值-如果没有设置,脚本应该出错。

396008 次浏览
${MyVariable:=SomeDefault}

如果MyVariable被设置并且不为空,它将重置变量值(=什么都不发生) 否则,MyVariable被设置为SomeDefault

上面的代码将尝试执行${MyVariable},所以如果你只想设置变量do:

MyVariable=${MyVariable:=SomeDefault}

试试这个:

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

您的问题取决于您正在使用的shell。

伯恩壳对你要找的东西没什么影响。

但是…

它确实有效,几乎无处不在。

试着远离csh。与伯恩的外壳相比,它添加的铃铛和哨子是好的,但它现在真的吱吱作响。如果你不相信我,试着在csh中分离出STDERR !(-):

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

${MyVariable:=SomeDefault}

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

你也有可能:

${MyVariable:-SomeDefault}

它只是用SomeDefault替换你使用这个构造的变量。它不会将值SomeDefault赋给变量,在遇到此语句后,MyVariable的值仍然为空。

我总是用:

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

恐怕也没那么简洁。

在CSH下有$?STATE。

参数扩展

显而易见的答案是使用一种特殊的参数展开形式:

: ${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?}构造可以移植回UNIX版本7和Bourne Shell(1978年左右)。${var:?}构造是最近的:我认为它是在大约1981年的System III UNIX中,但它可能在那之前就在PWB UNIX中了。因此,它在Korn Shell中,在POSIX Shell中,特别是Bash中。

它通常被记录在shell手册页的参数扩展部分中。例如,bash手册说:

${parameter:?word}

如果为空或未设置显示错误。如果parameter为null或未设置,则word的展开(或者如果word不存在,则相应的消息)将被写入标准错误,并且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建议应该修复的。我还没有验证它不反对表达式被括在双引号中的形式,但这是一个合理的假设,它将是OK的。

这也可以是一种方式:

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

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

#!/bin/sh -u

这将导致脚本退出,如果任何未绑定的变量潜伏在里面。

在我看来,最简单和最兼容的检查# !/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

上述解决方案都不符合我的目的,部分原因是我在开始一个漫长的过程之前检查了环境中需要设置的开放式变量列表。最后我得出了这个结论:

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}

我们可以写一个很好的断言来一次检查一堆变量:

#
# 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_var_not_null [-f] variable ...
#
function assert_var_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_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_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

这里有更多这样的断言:https://github.com/codeforester/base/blob/master/lib/assertions.sh

对于未来像我这样的人,我想更进一步,参数化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

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

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