在Bash中测试非零长度的字符串:[ -n "$var" ]或者[ "$var" ]

255

我见过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
或者
if [ ! "$var" ]; then
  # Do something when var is zero-length
fi
7个回答

523

编辑: 这是一个更完整的版本,显示了 [(也称为 test)和 [[ 之间的更多区别。

下表显示了变量是否被引用,是否使用单引号或双引号以及变量是否只包含空格是影响使用带有或不带有 -n/-z 的 test 来检查变量的适当性的因素。

     | 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(列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 https://dev59.com/LW865IYBdhLWcg3wU9CI
# 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

1
谢谢!我决定采用“在单括号中引用变量(第2a列)”的风格。在我看来,-n只会增加噪音并降低可读性。同样地,为了测试零长度或未设置,我将使用[ !“$var”]而不是[-z“$var”]。 - AllenHalsey
3
你对“[”和“[-n`”的图表(原帖中的第一个问题)表明它们是完全等价的,对吗? - hobs
1
@hobs:是的,使用哪个取决于哪个更清晰。您还会注意到,在使用Bash时,双括号形式更受欢迎。 - Dennis Williamson
1
因此,总结一下你的精彩表格,测试非空字符串的更清晰(“更好”)的方法是 [[" - hobs

54

就Bash而言,最好使用更强大的[[

常见情况

if [[ $var ]]; then   # var is set and it is not empty
if [[ ! $var ]]; then # var is not set or it is set to an empty string

以上两种结构看起来简洁易读,大多数情况下应该足够使用。

请注意,我们不需要在[[中引用变量扩展,因为不存在单词拆分和全局匹配的危险。 (word splittingglobbing)

为了避免 shellcheck 对于 [[ $var ]][[ ! $var ]] 的软投诉,我们可以使用 -n 选项。

罕见情况

如果我们需要区分“被设置为空字符串”与“根本未被设置”的罕见情况时,我们可以使用以下结构:

if [[ ${var+x} ]]; then           # var is set but it could be empty
if [[ ! ${var+x} ]]; then         # var is not set
if [[ ${var+x} && ! $var ]]; then # var is set and is empty

我们也可以使用 -v 命令进行测试:

if [[ -v var ]]; then             # var is set but it could be empty
if [[ ! -v var ]]; then           # var is not set
if [[ -v var && ! $var ]]; then   # var is set and is empty
if [[ -v var && -z $var ]]; then  # var is set and is empty

相关文章和文档

关于这个话题有很多相关的文章。以下是一些:


我不认为使用[[一定更好。这是个人偏好的问题。即使你引用的链接也建议始终使用[] - Stack Underflow

17

这里有更多的测试

如果字符串不为空,则为真:

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

如果字符串为空则为True:

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

这并没有回答问题提出者关于哪种方法更好的问题。 - codeforester

2
很遗憾,我一直没有找到为什么一开始会有两种语法的原因。这两种语法似乎都是在1979年随Unix第7版一起引入的。
1994年的单一UNIX规范更喜欢-n-z的语法。自那以后,每个版本的POSIX标准都完全复制了同样的建议,包括最新版本的POSIX.1-2017

The two commands:

test "$1"
test ! "$1"

could not be used reliably on some historical systems. Unexpected results would occur if such a string expression were used and $1 expanded to '!', '(', or a known unary primary. Better constructs are:

test -n "$1"
test -z "$1"

respectively.

我认为,如果你想严格遵循规范中的每个建议,那么你应该继续使用-n-z,但说真的,你需要兼容28年前的系统吗?
在我看来,if [ "$var" ]if [ ! "$var" ]的可读性使得这种语法成为明显的赢家。但最重要的是,它们在功能上没有区别,所以按照个人喜好选择即可。

0

正确答案如下:

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

请注意使用[[...]],它可以正确地为您引用变量。

2
在Bash中,为什么即使不需要也要使用“-n”? - codeforester
@codeforester BASH(Bourne Again Shell)是Bourne Shell的后代,因此它继承了“-n”和“-z”测试运算符,但又在其基础上添加了“!”否定运算符。此外,这在概念上与高级语言中提供肯定和否定运算符的“==”和“!=”之间的区别相同,只是为了扩展可能的语义范围。有时,组装一个不需要依赖于双重否定的表达式要容易得多,等等。 - ingyhere

0

评估空环境变量的另一种可能更透明的方法是使用...

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

12
这种做法非常老派,来自 Bourne shell 时代。不要延续这种老习惯。bash 比它的前辈更加强大。 - tbc0
4
即使使用早期的shell,也不需要这样做,如果你避免使用POSIX标准明确标记为过时的语法。[ "$ENV_VARIABLE" != "" ]将在每个具有符合POSIX标准的test实现的shell上运行--不仅包括bash,还包括ash/dash/ksh等。 - Charles Duffy
2
换句话说,不要使用“-a”或“-o”来组合测试,而是使用“[...] && [...]”或“[...] || [...]”,并且在测试中使用任意变量数据的唯一边界情况是明确关闭的。 - Charles Duffy

-6

使用 case/esac 进行测试:

case "$var" in
  "") echo "zero length";;
esac

17
不,请不要这样。case不是用来做这个的。 - tbc0
2
case 在有超过两个选择时发挥最佳作用。 - codeforester
case 不适用于这种情况。 - Luis Lavaire.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接