检查数组在Bash中是否为空


110

我有一个数组,在脚本运行时会充满不同的错误消息。

我需要一种方法来检查脚本的末尾是否为空,如果有的话,请采取特定的措施。

我已经尝试将其视为普通VAR并使用-z进行检查,但这似乎行不通。有没有一种方法可以检查Bash中数组是否为空?

Answers:


143

假设您的数组是$errors,只需检查元素数是否为零即可。

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi

10
请注意,这=是一个字符串运算符。在这种情况下,它可以正常工作,但是我会改用正确的算术运算符-eq(以防万一我想切换到-geor -lt等)。
musiphil

6
不适用于set -u:“ unbound variable”-如果数组为空。
伊戈尔(Igor)

@Igor:在Bash 4.4中为我工作。set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty。如果为I unset foo,则显示foo: unbound variable,但有所不同:数组变量根本不存在,而不是存在且为空。
彼得·科德斯

使用时也在Bash 3.2(OSX)中进行了测试set -u-只要您先声明变量,就可以完美运行。
zeroimpl

15

在这种情况下,我通常使用算术扩展:

if (( ${#a[@]} )); then
    echo not empty
fi

干净整洁!我喜欢。我还注意到,如果数组的第一个元素始终为非空,(( ${#a} ))(第一个元素的长度)也将起作用。但是,这将在上失败a=(''),而(( ${#a[@]} ))答案中给出的将成功。
cxw

8

您也可以将数组视为简单变量。这样,只需使用

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

或使用另一面

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

与解决方案的问题是,如果一个数组声明如下:array=('' foo)。这些检查将报告该数组为空,而显然不是。(感谢@musiphil!)

使用[ -z "$array[@]" ]显然也不是解决方案。未指定大括号会尝试将其解释$array为字符串([@]在这种情况下为简单文字字符串),因此始终报告为false:“文字字符串是否为[@]空?” 显然不是。


7
[ -z "$array" ][ -n "$array" ]不起作用。尝试使用array=('' foo); [ -z "$array" ] && echo emptyempty即使array明显不是空白,它也会打印。
musiphil

2
[[ -n "${array[*]}" ]]将整个数组插入为字符串,并检查其长度是否为非零。如果您认为array=("" "")是空的,而不是拥有两个空的元素,这可能会很有用。
彼得·科德斯

@PeterCordes我认为不行。该表达式的计算结果为单个空格字符,并且[[ -n " " ]]为“ true”,真可惜。您的评论正是我想做的
Michael

@迈克尔:胡扯,你是对的。它仅适用于1元素的空字符串数组,而不适用2元素。我什至检查了较旧的bash,但那里还是错误的。就像你说的set -x显示了它如何扩展。我想我在发布之前没有测试该评论。>。<您可以通过设置IFS=''(在此语句周围保存/恢复它)来使其工作,因为"${array[*]}"扩展使用IFS的第一个字符分隔元素。(如果未设置,则为空格)。但是,“ 如果IFS为null,则在不插入分隔符的情况下连接参数。 ”(用于$ *位置参数的文档,但对于数组,我假设相同)。
彼得·科德斯

@Michael:添加了执行此操作的答案。
Peter Cordes

3

我检查了bash-4.4.0

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

bash-4.1.5

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

在后一种情况下,您需要以下构造:

${array[@]:+${array[@]}}

使其不会在空数组或未设置数组上失败。那就是你做得set -eu像我平时一样。这提供了更严格的错误检查。从文档

-e

如果可能由单个简单命令(请参见简单命令),列表(请参见列表)或复合命令(请参见复合命令)组成的管道(请参见“管道”)返回非零状态,则立即退出。如果失败的命令紧随一段时间或直到关键字,if语句中的测试部分,&&或||中执行的任何命令的一部分之后,命令列表中的一部分,则外壳程序不会退出。列出最后一个&&或||之后的命令,管道中最后一个命令之外的任何命令,或者如果命令的返回状态用!反转。如果除子外壳程序以外的复合命令由于在-e被忽略时命令失败而返回非零状态,则外壳程序不会退出。退出外壳之前,将执行ERR上的陷阱(如果已设置)。

此选项分别适用于外壳环境和每个子外壳环境(请参阅命令执行环境),并且可能导致子外壳在执行子外壳中的所有命令之前退出。

如果复合命令或shell函数在忽略-e的上下文中执行,则即使设置了-e且命令返回a故障状态。如果复合命令或shell函数在忽略-e的上下文中执行时设置了-e,则该设置在复合命令或包含该函数调用的命令完成之前不会生效。

-u

执行参数扩展时,将特殊参数“ @”或“ *”以外的未设置变量和参数视为错误。错误消息将被写入标准错误,并且非交互式外壳将退出。

如果不需要,请随时省略:+${array[@]}

还要注意,[[在这里使用运算符很重要,随着[您得到:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty

-u您一起应该实际使用${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski

@JakubBochenski您正在谈论哪个版本的bash?gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

单括号示例中的问题@肯定是。您可以使用*类似的数组扩展[ "${array[*]}" ],可以吗?不过,[[也可以。对于具有多个空字符串的数组,这两种方式的行为都令人惊讶。这两个[ ${#array[*]} ][[ "${array[@]}" ]]是假的array=()array=(''),但真正的array=('' '')(两个或两个以上空字符串)。如果您希望一个或多个空字符串全部为true,则可以使用[ ${#array[@]} -gt 0 ]。如果您希望它们全部为假,则可以将//它们淘汰。
eisd

我可以使用@eisd [ "${array[*]}" ],但是如果遇到这种表达,对我来说很难理解它的作用。由于[...]根据字符串对插值结果进行运算。与相对[[...]],它可以知道所插值的内容。也就是说,它可以知道它已传递给数组。[[ ${array[@]} ]]对我来说是“检查数组是否为非空”,而[ "${array[*]}" ]作为“检查所有数组元素的插值结果是否为非空字符串”。
x-yuri

...对于有两个空字符串的行为,这一点都不令我感到惊讶。令人惊讶的是一个空字符串的行为。但是可以说是合理的。关于[ ${#array[*]} ],您可能是想说的[ "${array[*]}" ],因为前者适用于任何数量的元素。因为元素数总是非空字符串。对于具有两个元素的后者,括号内的表达式扩展' '为非空字符串。至于[[ ${array[@]} ]],他们只是(正确地认为)两个元素的任何数组都是非空的。
x-yuri

2

如果要检测带有空元素的数组,如arr=("" "")空,则与arr=()

您可以将所有元素粘贴在一起,然后检查结果是否为零长度。(如果数组可能很大,则构建数组内容的展平副本对于性能而言不是理想的选择。但是希望您不对此类程序使用bash ...)

"${arr[*]}"以的第一个​​字符分隔的元素扩展IFS。因此,您需要保存/恢复IFS并执行IFS=''此操作,否则请检查字符串长度==数组元素的数量-1。(n元素数组具有n-1分隔符)。要一一处理,最简单的方法是将连接数加1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

测试用例 set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

不幸的是,这失败了arr=()[[ 1 -ne 0 ]] 因此,您需要先单独检查实际为空的数组。


或与IFS=''。可能您想保存/还原IFS而不是使用子外壳,因为您不能轻易地从子外壳中获得结果。

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

例:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

确实可以使用arr=()-它仍然只是空字符串。


我赞成,但是我已经开始使用[[ "${arr[*]}" = *[![:space:]]* ]],因为我可以指望至少一个非WS字符。
迈克尔·

@Michael:是的,如果您不需要拒绝,那是一个不错的选择arr=(" ")
Peter Cordes

0

就我而言,第二个答案还不够,因为可能会有空格。我来了:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi

echo | wc与使用Shell内置程序相比,似乎不必要地效率低下。
彼得·科德斯

不知道我是否了解@PeterCordes,是否可以[ ${#errors[@]} -eq 0 ];以解决空白问题的方式修改第二个答案?我也希望内置。
Micha

空格到底是怎么引起问题的? $#扩展为一个数字,即使在之后也可以正常工作opts+=("")。例如unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}",我得到2。您可以举一个不起作用的例子吗?
彼得·科德斯

很久以前了 IIRC原始源始终至少打印“”。因此,对于opts =“”或opts =(“”),我需要0,而不是1,忽略空的换行符或空字符串。
Micha

好的,所以您需要将opts=("")其视为与opts=()?这不是一个空数组,但是您可以使用来检查是否有空数组或第一个元素为空opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty。请注意,您当前的回答是对表示“无选择” opts=("" "-foo"),这完全是伪造的,这会重现该行为。 您可能会[[ -z "${opts[*]}" ]]猜到要将所有数组元素插值到一个平面字符串中,该字符串-z检查非零长度。 如果检查第一个元素已足够,则-z "$opts"可以使用。
彼得·科德斯

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.