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

时间:2008-11-21 01:11:28

标签: bash 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

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

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

14 个答案:

答案 0 :(得分:558)

参数扩展

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

: ${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获得了“#”评论,并且冒号大会被淘汰了。但是如果你遇到一个以冒号开头的脚本,现在你就会明白为什么。


双引号的位置

blongcomment

中提问
  

对此讨论有何想法? 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建议应该修复的内容。我没有验证它不反对表达式用双引号括起来的表单,但是可以合理地假设它没问题。

答案 1 :(得分:122)

试试这个:

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

答案 2 :(得分:55)

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

伯恩贝壳对你所追求的东西的影响很小。

但是...

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

试着远离csh。与Bourne外壳相比,这对它所添加的铃声和口哨很有好处,但它现在真的吱吱作响。如果您不相信我,请尝试在csh中分离出STDERR! ( - :

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

${MyVariable:=SomeDefault}

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

你也有可能:

${MyVariable:-SomeDefault}

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

答案 3 :(得分:47)

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

#!/bin/sh -u

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

答案 4 :(得分:37)

${MyVariable:=SomeDefault}

如果MyVariable设置为非空,则会重置变量值(=没有任何反应) 否则,MyVariable设置为SomeDefault

以上将尝试执行${MyVariable},因此如果您只想设置变量,请执行以下操作:

MyVariable=${MyVariable:=SomeDefault}

答案 5 :(得分:20)

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

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

同样,这适用于/ bin / sh,并且在旧的Solaris系统上也兼容。

答案 6 :(得分:15)

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

答案 7 :(得分:14)

我一直用:

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

我不敢那么简洁。

在CSH下你有$?STATE。

答案 8 :(得分:3)

对于像我这样的未来人,我想向前迈出一步并参数化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

答案 9 :(得分:2)

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

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

答案 10 :(得分:1)

这也是一种方式:

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

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

答案 11 :(得分:1)

上述解决方案都不适用于我的目的,部分原因是我在开始冗长的流程之前检查环境是否需要设置的开放式变量列表。我最终得到了这个:

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}

答案 12 :(得分:-1)

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

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

答案 13 :(得分:-7)

$?语法非常简洁:

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