ChatGPT解决这个技术问题 Extra ChatGPT

检查环境变量是否在 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

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

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

这个问题的许多答案感觉就像您会在 Code Golf Stack Exchange 上看到的一样。

C
Community

参数扩展

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

: ${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 或未设置,则显示错误。如果 parameter 为 null 或未设置,则 word 的扩展(或如果 word 不存在,则将显示消息)写入标准错误,并且 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)时,我收到以下消息:在 script.sh 第 13 行::${FOO:?"The environment variable 'FOO' must be set and non-empty"} ^ -- SC2086:双引号以防止通配符和分词。在这种情况下我应该做什么有什么建议吗?

简短的回答是“按照 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/…
D
David Schlosnagle

尝试这个:

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

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

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

Bourne shell 对您所追求的东西的影响很小。

但...

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

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

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

${MyVariable:=SomeDefault}

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

您还可以:

${MyVariable:-SomeDefault}

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


CSH: ( foo > foo.out ) >& foo.err
Bourne shell 执行所需的操作。
J
Jens

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

#!/bin/sh -u

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


b
bbarker
${MyVariable:=SomeDefault}

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

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

MyVariable=${MyVariable:=SomeDefault}

这不会生成消息 - Q 表示没有默认值(尽管在您输入答案时它没有说)。
正确版本:(1) : ${MyVariable:=SomeDefault} 或 (2) : MyVariable=${MyVariable:-SomeDefault}
A
Adriano

在我看来,#!/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" 仍会扩展为空字符串。
c
chepner

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

M
Mr.Ree

我一直用:

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

恐怕没有那么简洁。

在 CSH 下你有 $?STATE。


这会检查 STATE 是否为空或未设置,而不仅仅是未设置。
D
Denis Besic

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

c
codeforester

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

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


J
Jonathan Leffler

这也可以是一种方式:

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

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


注意:输出目录。是在未设置变量时使错误消息静音。
G
Gudlaugur Egilsson

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

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}

n
nafdef

我倾向于在登录 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 非空字符串是另一回事。
G
Graeme

$? 语法非常简洁:

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

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