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


500

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

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

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


2
这个问题的许多答案就像您在Code Golf Stack Exchange上看到的一样。
Daniel Schilling

Answers:


587

参数扩展

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

: ${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?}构造可移植回版本7 UNIX和Bourne Shell(1978或其附近)。的${var:?}构造稍晚一些:我认为它是在1981年左右的System III UNIX中使用的,但在此之前它可能已经在PWB UNIX中使用了。因此,它位于Korn Shell和POSIX Shell(特别是Bash)中。

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

${parameter:?word}

如果为空或未设置,则显示错误。如果parameter为null或未设置,则将word的扩展名(或如果不存在word则显示一条消息)写入标准错误,并且如果shell不是交互式的,则退出shell。否则,将替换参数的值。

冒号命令

我可能应该补充一点,冒号命令只是对它的参数求值,然后成功。这是原始的shell注释符号(在' #'到行尾)。长期以来,Bourne shell脚本的首字符是冒号。C Shell将读取脚本并使用第一个字符来确定它是用于C Shell(#“哈希”)还是Bourne Shell(:“冒号”)。然后内核参与进来,并增加了对' #!/path/to/program'的支持,而Bourne shell得到了' #'注释,冒号约定就此搁置了。但是,如果遇到以冒号开头的脚本,现在您将知道原因。


双引号的位置

布隆评论中问:

对这个讨论有什么想法吗?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
$ 

请注意,当整个表达式不在双引号中时,in的值$x将首先扩展到*文件名列表。这是shellcheck建议应修复的问题。我尚未验证它是否不反对将表达式用双引号引起来的形式,但这是一个合理的假设,那就是可以。


2
那就是我所需要的。自1987年以来,我一直在使用各种版本的Unix,但我从未见过这种语法-只是显示了……
AndrewR

它是如何工作的,在哪里记录?我想知道是否可以修改它以检查变量是否存在并将其设置为特定值。
jhabbott

6
它记录在shell手册页或Bash手册中,通常在标题“ Parameter Expansion”下。检查变量是否存在并将其设置为特定值(例如“ abc”)的标准方法很简单:if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi
乔纳森·莱夫勒

如何对带有空格的字符串变量使用此语法。当我设置要检查的变量为“ This is a test”时,出现错误消息“ This:not found”。有任何想法吗?
user2294382

1
对这个讨论有什么想法吗?github.com/koalaman/shellcheck/issues/…–

138

尝试这个:

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

8
太冗长了。分号是多余的。
乔纳森·莱夫勒

3
甚至更短的信息,[ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } 请参阅此博客文章
zipizap 2012年

36
那是可读的。我全都为此。Bash脚本需要他们在该部门可以获得的所有帮助!
伊恩·邓肯

原始答案的语法更加一致。
快速牙

4
但是,这不能区分未设置的变量和设置为空字符串的变量。
chepner

57

您的问题取决于您使用的外壳。

伯恩贝壳留下的痕迹很少。

但...

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

只需尝试远离csh。与Bourne贝壳相比,它增加了铃铛和哨子,但现在确实吱吱作响。如果您不相信我,请尝试在csh中分离出STDERR!(-:

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

${MyVariable:=SomeDefault}

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

您还可以:

${MyVariable:-SomeDefault}

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


3
CSH:(foo> foo.out)>&foo.err
Mr.Ree

Bourne shell执行所需的操作。
乔纳森·勒夫勒

51

当然最简单的方法是将添加-u切换到家当(在你的脚本的顶部线),假设你使用bash

#!/bin/sh -u

如果未绑定的变量潜伏其中,这将导致脚本退出。


40
${MyVariable:=SomeDefault}

如果MyVariable设置并且不为null,它将重置变量值(=不会发生任何事情)。
否则,MyVariable设置为SomeDefault

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

MyVariable=${MyVariable:=SomeDefault}

这不会生成消息-Q表示没有默认值(尽管在键入答案时没有说默认值)。
乔纳森·莱夫勒

10
正确的版本:(1):$ {MyVariable:= SomeDefault}或(2):MyVariable = $ {MyVariable:-SomeDefault}
乔纳森·莱夫勒

23

我认为#!/ bin / sh最简单,最兼容的检查是:

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

同样,这是针对/ bin / sh的,并且在旧的Solaris系统上也兼容。


这个“作品”,但即使是旧的Solaris系统上有一个/bin/sh支持${var:?}符号等
乔纳森·莱弗勒

迄今为止最好的答案。
2016年

4
设置为空字符串不同于根本不设置。该测试将在unset MYVAR和后面都显示“不存在” MYVAR=
chepner

@chepner,我对您的评论提出了宝贵的建议,但随后我继续进行了测试(使用bash),并且无法将var设置为一个空字符串,除非将其设置为unset。你会怎么做?
Sz。

@Sz我不确定您的意思。“未设置”表示该名称完全没有赋值。如果foo未设置,则"$foo"仍扩展为空字符串。
chepner '18

17

bash4.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

16

我一直使用:

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

恐怕没有那么简洁了。

在CSH下,您有$?STATE。


2
这将检查是否STATE为空或未设置,而不仅仅是未设置。
chepner

7

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

3

我们可以编写一个不错的断言来一次检查所有变量:

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



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}

-1

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

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

1
错了 is_this_an_env_variable P将返回成功,因为PATH存在。
埃里克(Eric)

之所以存在是因为它是环境变量。是否设置eqaul nonnull字符串是另一回事。
nafdef

-7

$?语法是非常整洁的:

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

这可行,但是这里有人知道我在哪里可以找到有关该语法的文档吗?$?通常是先前的返回值。
Danny Staple 2010年

我的错-这似乎不起作用。尝试使用带有和不带有该设置的简单脚本。
Danny Staple 2010年

11
在Bourne,Korn,Bash和POSIX Shell中,$?是上一个命令的退出状态;$?BLAH是由前一个命令的退出状态与“ BLAH”串联而成的字符串。这根本不会测试变量$BLAH。(有传言说它可能或多或少地满足了要求csh,但最好将贝壳留在海边。)
Jonathan Leffler

就Bash而言,这是完全错误的答案。
codeforester
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.