【问题标题】:How to tell if a string is not defined in a Bash shell script如何判断字符串是否未在 Bash shell 脚本中定义
【发布时间】:2010-09-18 17:13:02
【问题描述】:

如果我想检查空字符串,我会这样做

[ -z $mystr ]

但是如果我想检查变量是否已经被定义呢?还是 Bash 脚本没有区别?

【问题讨论】:

  • 请养成使用 [ -z "$mystr" ] 而不是 [ -z $mystr ] 的习惯
  • 如何做相反的事情?我的意思是当字符串不为空时
  • @flow:那[ -n "${VAR+x}"] && echo not null
  • @CharlesDuffy 我以前在很多在线资源中都读过这个。为什么首选这个?
  • @Ayos 因为如果您没有引号,则变量的内容是字符串拆分和通配符;如果是空字符串,则变为[ -z ] 而不是[ -z "" ];如果有空格,则变为[ -z "my" "test" ] 而不是[ -z my test ];如果是[ -z * ],则* 将替换为您目录中的文件名。

标签: bash shell scripting string null


【解决方案1】:

我认为Vinkoanswer 暗示了(如果没有说明)您所追求的答案,尽管它没有简单地拼写出来。要区分 VAR 是否设置但为空或未设置,您可以使用:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

您可能可以将第二行的两个测试合二为一:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

但是,如果您阅读 Autoconf 的文档,您会发现他们不建议将术语与“-a”结合使用,而是建议将单独的简单测试与&& 结合使用。我没有遇到有问题的系统;这并不意味着它们过去不存在(但它们现在可能极为罕见,即使它们在遥远的过去并不那么罕见)。

您可以在 Bash 手册中找到这些以及其他相关的shell parameter expansionstest or [ 命令和conditional expressions 的详细信息。


最近有人通过电子邮件向我询问这个​​问题的答案:

您使用了两个测试,我很了解第二个,但不是第一个。更确切地说,我不明白变量扩展的必要性

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi

这不也能达到同样的效果吗?

if [ -z "${VAR}" ]; then echo "VAR is not set at all"; fi

公平的问题 - 答案是“不,您更简单的替代方案不会做同样的事情”。

假设我在你的测试之前写了这个:

VAR=

您的测试会说“根本没有设置 VAR”,但我的测试会说(暗示因为它没有回显)“VAR 已设置,但它的值可能为空”。试试这个脚本:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo "JL:1 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:1 VAR is not set at all"; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo "JL:2 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:2 VAR is not set at all"; fi
)

输出是:

JL:1 VAR 根本没有设置 MP:1 VAR 根本没有设置 MP:2 VAR 根本没有设置

在第二对测试中,变量被设置,但它被设置为空值。这就是 ${VAR=value}${VAR:=value} 符号的区别。 ${VAR-value}${VAR:-value}${VAR+value}${VAR:+value},等等。


正如Gili 在他的answer 中指出的那样,如果您使用set -o nounset 选项运行bash,那么上面的基本答案会因unbound variable 而失败。它很容易补救:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

或者您可以使用set +u 取消set -o nounset 选项(set -u 相当于set -o nounset)。

【讨论】:

  • 我对这个答案的唯一问题是它以一种相当间接和不清楚的方式完成了它的任务。
  • 对于那些想要在 bash 手册页中查找上述含义的描述的人,请查找“参数扩展”部分,然后查找以下文本:“当不执行子字符串扩展时,使用下面记录的表格,bash 测试未设置的参数或 null ['null' 表示空字符串]。省略冒号会导致仅测试未设置的参数 (...) ${parameter:+word } : 使用备用值。如果参数为 null 或未设置,则不替换任何内容,否则替换单词的扩展。"
  • @Swiss 这既不是不清楚也不是间接的,而是惯用的。也许对于不熟悉 ${+}${-} 的程序员来说,不清楚,但如果要成为一名合格的 shell 用户,熟悉这些结构是必不可少的。
  • 如果您想在启用set -o nounset 的情况下实现此功能,请参阅stackoverflow.com/a/20003892/14731
  • 我对此做了很多测试;因为我的脚本中的结果不一致。我建议人们使用 bash-s v4.2 和 beyond 研究“[ -v VAR ]”方法。
【解决方案2】:
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z 也适用于未定义的变量。要区分未定义和已定义,您可以使用here 列出的内容,或者更清楚地解释为here

最干净的方法是使用这些示例中的扩展。要获得所有选项,请查看手册的参数扩展部分。

替代词:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

默认值:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

当然,您会以不同的方式使用其中之一,放置您想要的值而不是“默认值”,并在适当的情况下直接使用扩展。

【讨论】:

  • 但我想区分字符串是“”还是从未定义过。这可能吗?
  • 这个答案确实告诉了如何区分这两种情况;按照它提供的 bash 常见问题解答链接进行更多讨论。
  • 我在查尔斯发表评论后补充说
  • 在 bash 手册页中查找所有这些“技巧”的“参数扩展”。例如。 ${foo:-default} 使用默认值,${foo:=default} 分配默认值,${foo:?error message} 如果 foo 未设置,则显示错误消息,等等。
【解决方案3】:

高级 bash 脚本编写指南,10.2。参数替换:

  • ${var+blahblah}:如果定义了 var,则用 'blahblah' 代替 表达式,否则替换为 null
  • ${var-blahblah}:如果 var 被定义,它本身被替换,否则 'blahblah' 被替换
  • ${var?blahblah}:如果定义了 var,则替换它,否则 函数存在,并带有“blahblah”作为错误消息。


要根据变量 $mystr 是否已定义您的程序逻辑,您可以执行以下操作:

isdefined=0
${mystr+ export isdefined=1}

现在,如果 isdefined=0 则变量未定义,如果 isdefined=1 则变量已定义

这种检查变量的方法比上面的答案更好,因为它更优雅、更易读,而且如果你的 bash shell 配置为在使用未定义变量(set -u) 时出错,脚本将提前终止。


其他有用的东西:

如果 $mystr 未定义,则为其分配默认值 7,否则保持原样:

mystr=${mystr- 7}

如果变量未定义,则打印错误消息并退出函数:

: ${mystr? not defined}

请注意,我在这里使用了 ':',以免 $mystr 的内容被作为命令执行,以防万一它被定义。

【讨论】:

  • 我不知道 ? BASH 变量的语法。这是一个很好的收获。
  • 这也适用于间接变量引用。这通过:var= ; varname=var ; : ${!varname?not defined} 并终止:varname=var ; : ${!varname?not defined}。但是使用set -u 是一个好习惯,这样做更容易。
【解决方案4】:

测试摘要。

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

【讨论】:

  • bash 中的良好实践。有时文件路径有空格,或防止命令注入
  • 我认为 ${var+x} 没有必要,除非变量 names 允许在其中包含空格。
  • 不只是好的做法;使用[ 需要 以获得正确的结果。如果var 未设置或为空,则[ -n $var ] 减少为退出状态为0 的[ -n ],而[ -n "$var" ] 的预期(和正确)退出状态为1。
【解决方案5】:

检查正在定义的变量的显式方法是:

[ -v mystr ]

【讨论】:

  • 在任何手册页(用于 bash 或测试)中都找不到这个,但它确实对我有用。知道添加了什么版本吗?
  • 它记录在Conditional Expressions 中,引用自Bourne Shell Builtins 部分末尾的test 运算符。不知道是什么时候添加的,但不是苹果版的bash(基于bash 3.2.51),所以大概是4.x的功能。
  • 这对我来说不适用于GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)。错误是-bash: [: -v: unary operator expected。根据here 的评论,它至少需要 bash 4.2 才能工作。
  • 感谢您检查它何时可用。我以前在各种系统上使用过它,但突然它就不起作用了。我是出于好奇来到这里的,是的,这个系统上的 bash 当然是 3.x 的 bash。
【解决方案6】:

https://stackoverflow.com/a/9824943/14731 包含一个更好的答案(一个更具可读性并且与启用set -o nounset 一起使用)。它的工作原理大致是这样的:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

【讨论】:

    【解决方案7】:

    另一种选择:the "list array indices" expansion:

    $ unset foo
    $ foo=
    $ echo ${!foo[*]}
    0
    $ foo=bar
    $ echo ${!foo[*]}
    0
    $ foo=(bar baz)
    $ echo ${!foo[*]}
    0 1
    

    只有当foo 未设置时才会扩展为空字符串,因此您可以使用条件字符串进行检查:

    $ unset foo
    $ [[ ${!foo[*]} ]]; echo $?
    1
    $ foo=
    $ [[ ${!foo[*]} ]]; echo $?
    0
    $ foo=bar
    $ [[ ${!foo[*]} ]]; echo $?
    0
    $ foo=(bar baz)
    $ [[ ${!foo[*]} ]]; echo $?
    0
    

    应该在任何 bash 版本 >= 3.0 中可用

    【讨论】:

      【解决方案8】:

      不想让这辆自行车进一步脱落,但想添加

      shopt -s -o nounset
      

      是您可以添加到脚本顶部的内容,如果变量未在脚本中的任何位置声明,则会出错。您看到的消息是unbound variable,但正如其他人提到的那样,它不会捕获空字符串或空值。为了确保任何单个值都不为空,我们可以测试一个变量,因为它使用${mystr:?} 扩展,也称为美元符号扩展,这会与parameter null or not set 发生错误。

      【讨论】:

        【解决方案9】:

        Bash Reference Manual 是有关 bash 的权威信息来源。

        下面是一个测试变量是否存在的例子:

        if [ -z "$PS1" ]; then
                echo This shell is not interactive
        else
                echo This shell is interactive
        fi
        

        (来自section 6.3.2。)

        请注意,打开的[ 之后和] 之前的空格不是可选的


        给 Vim 用户的提示

        我的脚本有几个声明如下:

        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
        

        但我希望他们遵循任何现有的价值观。所以我把它们改写成这样:

        if [ -z "$VARIABLE_NAME" ]; then
                export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
        fi
        

        我能够使用快速正则表达式在 vim 中自动执行此操作:

        s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc
        

        这可以通过在视觉上选择相关行,然后输入: 来应用。命令栏预填充了:'<,'>。粘贴上面的命令并回车。


        在这个版本的 Vim 上测试:

        VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
        Compiled by root@apple.com
        

        Windows 用户可能需要不同的行尾。

        【讨论】:

          【解决方案10】:

          这是我认为检查变量是否已定义的更清晰的方法:

          var_defined() {
              local var_name=$1
              set | grep "^${var_name}=" 1>/dev/null
              return $?
          }
          

          如下使用:

          if var_defined foo; then
              echo "foo is defined"
          else
              echo "foo is not defined"
          fi
          

          【讨论】:

          • 写一个函数也不错;调用grep 很糟糕。请注意bash 支持${!var_name} 作为获取名称在$var_name 中指定的变量值的一种方式,因此name=value; value=1; echo ${name} ${!name} 产生value 1
          【解决方案11】:

          测试未定义变量的较短版本可以是:

          test -z ${mystr} && echo "mystr is not defined"
          

          【讨论】:

            【解决方案12】:

            不带任何参数的调用集合..它输出所有定义的变量..
            列表中的最后一个将是您的脚本中定义的那些..
            这样你就可以将它的输出通过管道传递给可以弄清楚什么是定义的,什么不是

            【讨论】:

            • 我没有对此投反对票,但破解一个相当小的坚果就像一把大锤。
            • 我实际上比接受的答案更喜欢这个答案。很清楚这个答案正在做什么。被接受的答案很奇怪。
            • 看来这个答案更像是实现“set”的副作用,而不是理想的结果。
            猜你喜欢
            • 2010-09-21
            • 1970-01-01
            • 2020-08-26
            • 1970-01-01
            • 1970-01-01
            • 2011-07-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多