English 中文(简体)
有什么简洁的方法在Unix shell脚本中检查环境变量是否设置?
原标题:
  • 时间:2008-11-21 01:11:27
  •  标签:

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

这需要输入很多内容。有没有更优雅的习语来检查一个环境变量集是否已设置?

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

最佳回答

Parameter Expansion

显然的答案是使用参数扩展的特殊形式之一:

: ${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。

如果您没有提供消息,系统会提供默认的消息。

${var?}结构可移植回到 Version 7 UNIX 和 Bourne Shell(大约在1978年)。${var:?}结构稍微新一些:我认为它是在 System III UNIX(大约在1981年)中出现的,但它可能先出现在 PWB UNIX 中。因此它存在于 Korn Shell 中,并在 POSIX shells 中,包括特别是 Bash。

它通常在 shell 的 man 页面中记录在名为 "Parameter Expansion" 的部分中。例如,bash 手册说:

${parameter:?word}

如果为Null或未设置,则显示错误。 如果参数为null或未设置,则会将单词扩展(或一条关于此单词的消息)写入标准错误流,并退出非交互式shell。否则,将替换参数的值。

The Colon Command

我应该补充一下,冒号命令只是对其参数进行求值,然后成功执行。这是原始的shell注释符号(在#到行末之前)。很长一段时间,Bourne shell脚本的第一个字符都是冒号。C Shell会读取脚本,并使用第一个字符来确定它是为C Shell(#哈希)还是Bourne shell(:冒号)而编写的。然后内核加入了支持#!/path/to/program,Bourne shell增加了#注释,冒号传统就被淘汰了。但是,如果您遇到以冒号开头的脚本,现在您将知道为什么。


Position of double quotes

评论中,blong提出了这个问题。

对于这个讨论有什么想法?https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

讨论的要点是:

但是,当我使用0.4.1版本的shellcheck进行检查时,我收到了这条信息:

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}"

To illustrate why, study the following. Note that the : command doesn t echo its arguments (but the shell does evaluate the arguments). We want to see the arguments, so the code below uses printf "%s " in place of :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s
" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s
" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s
" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s
" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s
" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s
" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s
" ${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。

Bourne shell 对你追求的东西几乎没有什么帮助。

但是...

它确实在几乎所有地方都可以工作。

尽量远离csh。与Bourne shell相比,它的铃铛和口哨增加了很多优点,但现在它已经开始出问题了。如果你不相信我,就尝试在csh中分离STDERR! (-:

There are two possibilities here. The example above, namely using:

${MyVariable:=SomeDefault}

第一次使用,您需要引用$MyVariable。 这将获取环境变量MyVariable,如果当前未设置,则为变量分配SomeDefault的值以供以后使用。

你也有可能:

${MyVariable:-SomeDefault}

这只是将SomeDefault替换为您使用此结构的变量。它不会将SomeDefault的值分配给变量,且在遇到此语句后,MyVariable的值仍将为null。

Surely the simplest approach is to add the -u switch to the shebang (the line at the top of your script), assuming you’re using bash:

#!/bin/sh -u

This will cause the script to exit if any unbound variables lurk within.

${MyVariable:=SomeDefault}

If MyVariable is set and not null, it will reset the variable value (= nothing happens).
Else, MyVariable is set to SomeDefault.

The above will attempt to execute ${MyVariable}, so if you just want to set the variable do:

MyVariable=${MyVariable:=SomeDefault}

In my opinion the simplest and most compatible check for #!/bin/sh is:

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

Again, this is for /bin/sh and is compatible also on old Solaris systems.

bash 4.2 introduced the -v operator which tests if a name is set to any value, even the empty string.

$ 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

I always used:

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

Not that much more concise, I m afraid.

Under CSH you have $?STATE.

For future people like me, I wanted to go a step forward and parameterize the var name, so I can loop over a variable sized list of variable names:

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

We can write a nice assertion to check a bunch of variables all at once:

#
# 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
  "Variable  $var  not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Sample invocation:

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

Output:

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

More such assertions here: https://github.com/codeforester/base/blob/master/lib/assertions.sh

This can be a way too:

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

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

None of the above solutions worked for my purposes, in part because I checking the environment for an open-ended list of variables that need to be set before starting a lengthy process. I ended up with this:

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}

Rather than using external shell scripts I tend to load in functions in my login shell. I use something like this as a helper function to check for environment variables rather than any set variable:

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




相关问题
热门标签