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

Posted

技术标签:

【中文标题】检查环境变量是不是在 Unix shell 脚本中设置的简洁方法是啥?【英文标题】:What's a concise way to check that environment variables are set in a Unix shell script?检查环境变量是否在 Unix shell 脚本中设置的简洁方法是什么? 【发布时间】:2010-09-23 09:04:33 【问题描述】:

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

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

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

【问题讨论】:

这个问题的许多答案感觉就像您在Code Golf Stack Exchange 上看到的一样。 【参考方案1】:
$MyVariable:=SomeDefault

如果设置了MyVariable 并且不为空,它将重置变量值(= 没有任何反应)。 否则,MyVariable 设置为 SomeDefault

上面会尝试执行$MyVariable,所以如果你只想设置变量do:

MyVariable=$MyVariable:=SomeDefault

【讨论】:

这不会生成消息 - 并且 Q 说没有默认值(尽管在您输入答案时它没有说)。 正确版本:(1) : $MyVariable:=SomeDefault 或 (2) : MyVariable=$MyVariable:-SomeDefault【参考方案2】:

试试这个:

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

【讨论】:

那相当冗长。分号是多余的。 或者更短的[ -z "$STATE" ] && echo "Need to set STATE"; exit 1; 见this blog post 什么是可读的。我完全赞成这个。 Bash 脚本需要他们可以在该部门获得的所有帮助! 原始答案在语法上更加一致。 然而,这并不区分未设置的变量和设置为空字符串的变量。【参考方案3】:

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

Bourne shell 几乎不会影响您的追求。

但是...

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

尽量远离 csh。与 Bourne shell 相比,它添加的花里胡哨很有用,但它现在真的吱吱作响。如果您不相信我,请尝试在 csh 中分离出 STDERR! (-:

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

$MyVariable:=SomeDefault

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

您还可以:

$MyVariable:-SomeDefault

它只是用 SomeDefault 代替您使用此构造的变量。它没有给变量赋值 SomeDefault ,遇到这个语句后 MyVariable 的值仍然为空。

【讨论】:

CSH: ( foo > foo.out ) >& foo.err Bourne shell 执行所需的操作。【参考方案4】:

我一直用:

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

恐怕没有那么简洁了。

在 CSH 下你有 $?STATE。

【讨论】:

这会检查 STATE 是否为空或未设置,而不仅仅是未设置。【参考方案5】:

参数扩展

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

: $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 手册页中名为 Parameter Expansion 的部分中。例如,bash 手册说:

$parameter:?word

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

冒号指挥部

我可能应该补充一点,冒号命令只是对其参数进行评估,然后成功。它是原始的 shell 注释符号(在 '#' 到行尾之前)。很长一段时间以来,Bourne shell 脚本都以冒号作为第一个字符。 C Shell 将读取脚本并使用第一个字符来确定它是用于 C Shell('#' 哈希)还是 Bourne shell(':' 冒号)。然后内核开始行动,增加了对'#!/path/to/program'的支持,Bourne shell 得到了'#' cmets,冒号约定被搁置了。但是如果你遇到一个以冒号开头的脚本,现在你就会知道为什么了。


双引号的位置

blong 在comment 中提问:

对这个讨论有什么想法吗? 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 建议应该修复的。我还没有验证它不反对表达式用双引号括起来的形式,但可以合理地假设它是可以的。

【讨论】:

这就是我需要的东西。自 1987 年以来,我一直在使用各种版本的 Unix,但我从未见过这种语法 - 只是为了展示...... 这是如何工作的,它记录在哪里?我想知道是否可以对其进行修改以检查变量是否存在并设置为特定值。 它记录在 shell 手册页或 Bash 手册中,通常在标题“参数扩展”下。检查变量是否存在并设置为特定值(比如'abc')的标准方法很简单:if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi 如何将此语法与带有空格的字符串变量一起使用。当我将要检查的变量设置为“这是一个测试”时,我收到一条错误消息“This: command not found”。有什么想法吗? 对这个讨论有什么想法吗? github.com/koalaman/shellcheck/issues/…【参考方案6】:

这也是一种方式:

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

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

【讨论】:

注意:输出目录。是在未设置变量时使错误消息静音。【参考方案7】:

$? 语法非常简洁:

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

【讨论】:

这行得通,但是这里有人知道我在哪里可以找到有关该语法的文档吗?美元?通常是之前的返回值。 我的错 - 这似乎不起作用。在一个简单的脚本中试一试,不管有没有那个集合。 在 Bourne、Korn、Bash 和 POSIX shell 中,$? 是上一个命令的退出状态; $?BLAH 是由前一个命令的退出状态与“BLAH”连接的字符串。这根本不测试变量$BLAH。 (有传言说它可能或多或少符合csh 的要求,但最好将贝壳留在海边。) 就 Bash 而言,这是一个完全错误的答案。【参考方案8】:

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

#!/bin/sh -u

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

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

这个“有效”,但即使是旧的 Solaris 系统也有一个支持$var:? 符号等的/bin/sh 迄今为止最好的答案。 设置为空字符串与根本没有设置是不同的。此测试将在unset MYVARMYVAR= 之后打印“不存在”。 @chepner,我赞成您对可能有价值的提示的评论,但后来我继续测试它(使用 bash),并且如果没有它,实际上也无法将 var 设置为空字符串未定。你会怎么做? @Sz 我不确定你的意思。 “未设置”意味着该名称根本没有分配给它的值。如果 foo 未设置,"$foo" 仍会扩展为空字符串。【参考方案10】:

bash 4.2 引入了-v 运算符,用于测试名称是否设置为 any 值,甚至是空字符串。

$ 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

【讨论】:

【参考方案11】:

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

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】:

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

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

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

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

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

【讨论】:

这是错误的。 is_this_an_env_variable P 将返回成功,因为 PATH 存在。 它的存在是因为它是一个环境变量。是否设置了 eqaul 非空字符串是另外一回事。

以上是关于检查环境变量是不是在 Unix shell 脚本中设置的简洁方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查环境变量是不是存在并获取其值? [复制]

Unix/Linux shell脚本中 “set -e” 的作用

理解 Linux/Unix 登录脚本的技巧

理解 Linux/Unix 登录脚本的技巧

Unix Tutorial Eight

unix环境下shell脚本如何往文本文件的头部加入utf8 bom头EEBBBF?如何删除bom