17

在 shell 脚本中测试变量是否为空/未定义的可移植和规范方法是什么?它应该在所有类似 sh 的 shell 中工作。我现在做的是这样的:

if [ -z "$var" ] ; then
    ...

而对于反向,当变量不为空/未定义时做一些事情:

if [ -n "$var" ] ; then
    ...

虽然这些适用于我现在编写的脚本,但我想知道一种方法,它可以在任何合理兼容的sh类 shell中工作,即使在比具有 GNU 用户空间和bashdash的 Linux PC 更晦涩的环境中也是如此。

我正在寻找在不同 shell 环境中经验丰富并且知道不同做事方式的陷阱的人的“专家答案”,而不是像“应该有效”这样的意见。

4

5 回答 5

12

支架结构[...]是 POSIX 标准的一部分,几乎可以在任何地方使用。因此,要测试变量是否为空,if问题中使用的情况是完美的:

if [ -z "$STRING" ]; then
    # $STRING is empty
fi

只要你记得:

总是引用变量

不带引号的变量会进行分词,因此如果您使用[ -z $STRING ](不带引号$STRING)并且$STRING包含空格或为空,shell 将看到[ -z SEVERAL WORDS ]or[ -z ]两者都是语法错误 - 即不是您想要的。

始终只使用一个等号

如果测试 POSIX shell 中的字符串是否为空,另一种方法是使用=.

if [ "$STRING" = "" ]; then
    # $STRING is empty
fi

但是请注意,您永远不应该在内部使用双等号 ( ==) [...]!(这只适用于 is bash,或者 whensh是一个链接,bash但如果在另一台机器上运行sh链接到其他东西,它将不起作用。)如果你想使用双等号,你还应该有双括号[[...]](仅适用于zshbash,但不适用于普通的 POSIX shell,如dash)。

不要使用&&||在里面[...]

可能适用于某些外壳,但肯定不会适用于所有外壳。相反,对于 '' 写[...] && [...]or [... -a ...]。而对于 ' or ' 写[...] || [...]or [... -o ...]

古代的东西

有时,在非常古老的 shell 脚本中,您会看到如下内容,以测试空变量。这仍然是完全有效的(但在我看来有点笨拙)而且我从来没有遇到过需要这个的 shell(虽然,不可否认,我的经验在 Linux 之外是有限的——也许还有 BSD shell 仍然需要这个?)

if [ ".$STRING" = "." ]; then
    # $STRING is empty
fi

句号的前置是为了避免括号构造在碰巧包含时[...]混淆,以及使用的其他选项。通过添加句点,它变成了一个非选项。$STRING-e-o[...]

不过,我从未见过 shell 需要这种解决方法。(Dash 当然不需要它们。)所以忽略这一点可能是安全的。

于 2014-07-23T00:41:20.283 回答
6

免责声明:我不认为自己是贝壳专家。

AFAIK,test命令和-zand-n标志是 POSIX 标准的一部分,所以你应该可以在许多平台上使用它们。

于 2013-11-05T14:34:49.080 回答
3

重要的是要记住,空的 shell 变量与未设置的 shell 变量不同——尽管不能使用[ test ]. 所test能做的就是报告变量扩展的值 - 决定这一点的是参数扩展。例如:

var=value
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently not empty.

再次:

var=
echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
###OUTPUT###
I am $var and I am set. I am currently empty.

[ test ]

{   _nstr() { echo 'I just evaluated a ""null string!' ; }
    [ -z "$var" ] && _nstr
    [ -z "" ] && _nstr
    unset var    
    [ -z "$var" ] && _nstr
    echo "${var+I am \$var and I am set. I am currently ${var:+not} empty.}"
}
###OUTPUT###
I just evaluated a ""null string!
I just evaluated a ""null string!
I just evaluated a ""null string!

希望这能把它带回家:

var=value
[ -z "${var+}" ] && _nstr
###OUTPUT###    
I just evaluated a ""null string!

如果您想可移植地确定变量是否为空,则可以这样做:

${var:+false} [ -n "${var+1}" ] && echo \$var is set and null

我认为您可以很好地利用 shell 的参数扩展来处理此类情况,在某些情况下甚至可以用一块石头杀死两只鸟(参见第一个示例)。这绝对应该是便携式的。以下是开放组的 POSIX shell 指南的不完整复制/粘贴,可在此处找到:http: //pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02

${parameter:-word}

  • 使用默认值。如果参数是unsetor null,则替换 word 的扩展;否则,应替换参数的值。

${parameter:=word}

  • 分配默认值。如果 parameter 是unsetor null,则将 word 的扩展分配给 parameter。在所有情况下,应替换参数的最终值。只能以这种方式分配变量,而不是位置参数或特殊参数。

${parameter:?[word]}

  • 如果为 Null 或未设置,则指示错误。如果参数是unsetor null,则 word 的扩展(或如果 word 省略,则指示它未设置的消息)应写入标准错误,并且 shell 以非零退出状态退出。否则,应替换参数的值。交互式外壳不需要退出。

${parameter:+word}

  • 使用替代价值。如果参数是unsetor nullnull则应替换;否则,用词的扩展代替。

例子:

${parameter:-word}

  • 在此示例中,ls仅当xisnull或时才执行unset。($( ls)命令替换符号在命令替换中解释。) ${x:-$(ls)}

${parameter:=word}

% unset X
% echo ${X:=abc}
abc

编辑:

我刚刚注意到 Ashish 的单线,因为粘贴它感觉有点便宜,我想做点什么。所以这里有一个 POSIX 参数扩展 set-test one-liner:

_JAIL="" ; echo "_JAIL is ${_JAIL:-unset or null.}"

好吧,我猜还是很便宜。

编辑2:

所以我只是注意到了这一点:“......虽然这些脚本适用于我现在编写的脚本,但我想知道一种方法,它可以在任何合理兼容的类似 sh 的 shell 中工作,即使在比 Linux 更晦涩的环境中也是如此具有 GNU 用户空间和 bash 或 dash 的 PC..."

抱歉,我个人不是专家,但我首先选择了以下方法来定义重要的 shell 变量,这些变量在挖掘了一些相当熟练编写的 initramfs busybox 脚本之后可能会或可能不会被传递。它几乎可以在任何地方按预期工作。

这有点漂亮,因为您只有在用户没有定义变量时才将变量设置为默认条件,而无需任何额外的工作来检查它们是否确实如此。当然还有很多其他用途,我只是不够聪明,无法像我应该的那样使用它们。

_VAR1=${1:-/default/path} ; _VAR2=${2:-"$_default_size"}

编辑3:

phs 正确地指定他的方法测试空变量而不是问题中提出的空变量。根据是否使用中心冒号,此方法可以执行相同的操作。我已经从下面的 POSIX 指南中复制了一个废话表来证明这一点。

{subs='substitute'; params='parameters'; assn='assign'; err='error'} 

         "$parameter" == |set && !null |set && null|  !set
       ------------------ ------------- ----------- ----------
      ${parameter:-word} | subs params | subs word | subs word
       ------------------ ------------- ----------- ----------
      ${parameter-word}  | subs params | subs null | subs word
       ------------------ ------------- ----------- ----------
      ${parameter:=word} | subs params | assn word | assn word
       ------------------ ------------- ----------- ----------
      ${parameter=word}  | subs params | subs null | assn word
       ------------------ ------------- ----------- ----------
      ${parameter:?word} | subs params | err; exit | err; exit
       ------------------ ------------- ----------- ----------
      ${parameter?word}  | subs params | subs null | err; exit
       ------------------ ------------- ----------- ----------
      ${parameter:+word} |  subs word  | subs null | subs null
       ------------------ ------------- ----------- ----------
      ${parameter+word}  |  subs word  | subs word | subs null
于 2013-11-20T00:27:25.240 回答
1

便携和规范是两个不同的东西。

对于便携式,我在几个autoconf脚本中看到的测试是:

if test "x$MY_VAR" = x; then
  # ...
fi

这考验的是空虚,而不是缺席。

于 2013-11-20T00:36:12.683 回答
0

除非您在测试变量中运行大代码,否则您不必进入 if else 阶梯

一个例子

_JAIL=""
[  -z "$_JAIL" ] && echo "No" || echo "Yes"

单击此处了解更多示例。

于 2013-11-05T09:53:10.787 回答