我见过Bash脚本用两种不同的方式测试长度为非零的字符串。大多数脚本使用-n选项:

#!/bin/bash
# With the -n option
if [ -n "$var" ]; then
  # Do something when var is non-zero length
fi

但是-n选项其实并不需要:

# Without the -n option
if [ "$var" ]; then
  # Do something when var is non-zero length
fi

哪一种方法更好?

类似地,哪种是测试零长度的更好方法:

if [ -z "$var" ]; then
  # Do something when var is zero-length
fi

or

if [ ! "$var" ]; then
  # Do something when var is zero-length
fi

当前回答

编辑:这是一个更完整的版本,显示了[(又名test)和[[之间的更多差异。

下表显示了变量是否带引号,使用单括号还是双括号,以及变量是否只包含空格,这些都影响使用带-n/-z的测试是否适合检查变量。

     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b
     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"
-----+------------------------------------+------------------------------------
unset| false false true  false true  true | false false false false true  true
null | false false true  false true  true | false false false false true  true
space| false true  true  true  true  false| true  true  true  true  false false
zero | true  true  true  true  false false| true  true  true  true  false false
digit| true  true  true  true  false false| true  true  true  true  false false
char | true  true  true  true  false false| true  true  true  true  false false
hyphn| true  true  true  true  false false| true  true  true  true  false false
two  | -err- true  -err- true  -err- false| true  true  true  true  false false
part | -err- true  -err- true  -err- false| true  true  true  true  false false
Tstr | true  true  -err- true  -err- false| true  true  true  true  false false
Fsym | false true  -err- true  -err- false| true  true  true  true  false false
T=   | true  true  -err- true  -err- false| true  true  true  true  false false
F=   | false true  -err- true  -err- false| true  true  true  true  false false
T!=  | true  true  -err- true  -err- false| true  true  true  true  false false
F!=  | false true  -err- true  -err- false| true  true  true  true  false false
Teq  | true  true  -err- true  -err- false| true  true  true  true  false false
Feq  | false true  -err- true  -err- false| true  true  true  true  false false
Tne  | true  true  -err- true  -err- false| true  true  true  true  false false
Fne  | false true  -err- true  -err- false| true  true  true  true  false false

如果你想知道一个变量的长度是否为非零,请执行以下任何操作:

在单括号中引用变量(第2a列) 使用-n并在单括号中引用变量(第4a列) 使用双括号,带引号或不带引号,带-n或不带-n(列1b - 4b)

请注意,从标记为“two”的行开始的列1a中,结果表明[正在计算变量的内容,就像它们是条件表达式的一部分一样(结果与描述列中的“T”或“F”所暗示的断言匹配)。当使用[[(列1b)时,变量内容被视为字符串而不计算。

列3a和5a中的错误是由变量值包含空格和变量未加引号引起的。同样,如列3b和5b所示,[[将变量的内容计算为字符串。

相应地,对于零长度字符串的测试,列6a、5b和6b显示了正确的方法。还要注意,如果否定比使用相反的操作更清楚地表明意图,则可以对任何这些测试执行否定。例如:if ![[-n $var]]。

如果使用[,确保不会得到意外结果的关键是引用变量。使用[[,没有关系。

被抑制的错误消息是“期望一元运算符”或“期望二元运算符”。

这是生成上面表格的脚本。

#!/bin/bash
# by Dennis Williamson
# 2010-10-06, revised 2010-11-10
# for http://stackoverflow.com/q/3869072
# designed to fit an 80 character terminal

dw=5    # description column width
w=6     # table column width

t () { printf '%-*s' "$w" " true"; }
f () { [[ $? == 1 ]] && printf '%-*s' "$w" " false" || printf '%-*s' "$w" " -err-"; }

o=/dev/null

echo '     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b'
echo '     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"'
echo '-----+------------------------------------+------------------------------------'

while read -r d t
do
    printf '%-*s|' "$dw" "$d"

    case $d in
        unset) unset t  ;;
        space) t=' '    ;;
    esac

    [ $t ]        2>$o  && t || f
    [ "$t" ]            && t || f
    [ -n $t ]     2>$o  && t || f
    [ -n "$t" ]         && t || f
    [ -z $t ]     2>$o  && t || f
    [ -z "$t" ]         && t || f
    echo -n "|"
    [[ $t ]]            && t || f
    [[ "$t" ]]          && t || f
    [[ -n $t ]]         && t || f
    [[ -n "$t" ]]       && t || f
    [[ -z $t ]]         && t || f
    [[ -z "$t" ]]       && t || f
    echo

done <<'EOF'
unset
null
space
zero    0
digit   1
char    c
hyphn   -z
two     a b
part    a -a
Tstr    -n a
Fsym    -h .
T=      1 = 1
F=      1 = 2
T!=     1 != 2
F!=     1 != 1
Teq     1 -eq 1
Feq     1 -eq 2
Tne     1 -ne 2
Fne     1 -ne 1
EOF

其他回答

这里还有一些测试

如果string不为空,则为True:

[ -n "$var" ]
[[ -n $var ]]
test -n "$var"
[ "$var" ]
[[ $var ]]
(( ${#var} ))
let ${#var}
test "$var"

string为空时为True:

[ -z "$var" ]
[[ -z $var ]]
test -z "$var"
! [ "$var" ]
! [[ $var ]]
! (( ${#var} ))
! let ${#var}
! test "$var"

不幸的是,我还没有找到为什么有两种语法放在首位。两者似乎都是在1979年Unix版本7中同时引入的。

1994年的单一Unix规范首选-n和-z语法。从那以后,同样的建议被逐字复制到POSIX的每个版本中,包括最新版本POSIX.1-2017:

这两条命令: 测试" $ 1 " 测试!" $ 1 " 不能在某些历史系统上可靠地使用。意想不到的 如果使用这样的字符串表达式并且$1 扩展为'!', '(',或已知的一元主元。更好的构念是: Test -n "$1" Test -z "$1" 分别。

我认为,如果你想学而不实地遵循规范中的每一条建议,那么你应该继续使用-n和-z,但说真的,你需要兼容超过28年的系统吗?

在我看来,if ["$var"]和if [!"$var"]使得该语法成为明显的赢家。但底线是没有功能上的区别,所以随你的个人喜好去吧。

计算空环境变量的另一种可能更透明的方法是使用…

  if [ "x$ENV_VARIABLE" != "x" ] ; then
      echo 'ENV_VARIABLE contains something'
  fi

正确答案如下:

if [[ -n $var ]] ; then
  blah
fi

注意[[…]]],它正确地处理变量的引用。

编辑:这是一个更完整的版本,显示了[(又名test)和[[之间的更多差异。

下表显示了变量是否带引号,使用单括号还是双括号,以及变量是否只包含空格,这些都影响使用带-n/-z的测试是否适合检查变量。

     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b
     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"
-----+------------------------------------+------------------------------------
unset| false false true  false true  true | false false false false true  true
null | false false true  false true  true | false false false false true  true
space| false true  true  true  true  false| true  true  true  true  false false
zero | true  true  true  true  false false| true  true  true  true  false false
digit| true  true  true  true  false false| true  true  true  true  false false
char | true  true  true  true  false false| true  true  true  true  false false
hyphn| true  true  true  true  false false| true  true  true  true  false false
two  | -err- true  -err- true  -err- false| true  true  true  true  false false
part | -err- true  -err- true  -err- false| true  true  true  true  false false
Tstr | true  true  -err- true  -err- false| true  true  true  true  false false
Fsym | false true  -err- true  -err- false| true  true  true  true  false false
T=   | true  true  -err- true  -err- false| true  true  true  true  false false
F=   | false true  -err- true  -err- false| true  true  true  true  false false
T!=  | true  true  -err- true  -err- false| true  true  true  true  false false
F!=  | false true  -err- true  -err- false| true  true  true  true  false false
Teq  | true  true  -err- true  -err- false| true  true  true  true  false false
Feq  | false true  -err- true  -err- false| true  true  true  true  false false
Tne  | true  true  -err- true  -err- false| true  true  true  true  false false
Fne  | false true  -err- true  -err- false| true  true  true  true  false false

如果你想知道一个变量的长度是否为非零,请执行以下任何操作:

在单括号中引用变量(第2a列) 使用-n并在单括号中引用变量(第4a列) 使用双括号,带引号或不带引号,带-n或不带-n(列1b - 4b)

请注意,从标记为“two”的行开始的列1a中,结果表明[正在计算变量的内容,就像它们是条件表达式的一部分一样(结果与描述列中的“T”或“F”所暗示的断言匹配)。当使用[[(列1b)时,变量内容被视为字符串而不计算。

列3a和5a中的错误是由变量值包含空格和变量未加引号引起的。同样,如列3b和5b所示,[[将变量的内容计算为字符串。

相应地,对于零长度字符串的测试,列6a、5b和6b显示了正确的方法。还要注意,如果否定比使用相反的操作更清楚地表明意图,则可以对任何这些测试执行否定。例如:if ![[-n $var]]。

如果使用[,确保不会得到意外结果的关键是引用变量。使用[[,没有关系。

被抑制的错误消息是“期望一元运算符”或“期望二元运算符”。

这是生成上面表格的脚本。

#!/bin/bash
# by Dennis Williamson
# 2010-10-06, revised 2010-11-10
# for http://stackoverflow.com/q/3869072
# designed to fit an 80 character terminal

dw=5    # description column width
w=6     # table column width

t () { printf '%-*s' "$w" " true"; }
f () { [[ $? == 1 ]] && printf '%-*s' "$w" " false" || printf '%-*s' "$w" " -err-"; }

o=/dev/null

echo '     | 1a    2a    3a    4a    5a    6a   | 1b    2b    3b    4b    5b    6b'
echo '     | [     ["    [-n   [-n"  [-z   [-z" | [[    [["   [[-n  [[-n" [[-z  [[-z"'
echo '-----+------------------------------------+------------------------------------'

while read -r d t
do
    printf '%-*s|' "$dw" "$d"

    case $d in
        unset) unset t  ;;
        space) t=' '    ;;
    esac

    [ $t ]        2>$o  && t || f
    [ "$t" ]            && t || f
    [ -n $t ]     2>$o  && t || f
    [ -n "$t" ]         && t || f
    [ -z $t ]     2>$o  && t || f
    [ -z "$t" ]         && t || f
    echo -n "|"
    [[ $t ]]            && t || f
    [[ "$t" ]]          && t || f
    [[ -n $t ]]         && t || f
    [[ -n "$t" ]]       && t || f
    [[ -z $t ]]         && t || f
    [[ -z "$t" ]]       && t || f
    echo

done <<'EOF'
unset
null
space
zero    0
digit   1
char    c
hyphn   -z
two     a b
part    a -a
Tstr    -n a
Fsym    -h .
T=      1 = 1
F=      1 = 2
T!=     1 != 2
F!=     1 != 1
Teq     1 -eq 1
Feq     1 -eq 2
Tne     1 -ne 2
Fne     1 -ne 1
EOF