在数组中检查索引或键的最简单方法是什么?

125

使用:

set -o nounset
  1. Having an indexed array like:

    myArray=( "red" "black" "blue" )
    

    What is the shortest way to check if element 1 is set?
    I sometimes use the following:

    test "${#myArray[@]}" -gt "1" && echo "1 exists" || echo "1 doesn't exist"
    

    I would like to know if there's a preferred one.

  2. How to deal with non-consecutive indexes?

    myArray=()
    myArray[12]="red"
    myArray[51]="black"
    myArray[129]="blue"
    

    How to quick check that 51 is already set for example?

  3. How to deal with associative arrays?

    declare -A myArray
    myArray["key1"]="red"
    myArray["key2"]="black"
    myArray["key3"]="blue"
    

    How to quick check that key2 is already used for example?

11个回答

170

检查元素是否设置(适用于索引数组和关联数组)

[ "${array[key]+abc}" ] && echo "exists"

基本上,${array[key]+abc} 的作用是:

  • 如果 array[key] 已定义,则返回 abc
  • 如果 array[key] 未定义,则不返回任何内容

参考文献:
  1. 请参见 Bash 手册中的参数展开和小注释。

如果省略冒号,该运算符只测试 参数 是否存在。

  1. 这个答案实际上是从这个 Stack Overflow 问题的答案中改编而来的。

一个包装函数:

exists(){
  if [ "$2" != in ]; then
    echo "Incorrect usage."
    echo "Correct usage: exists {key} in {array}"
    return
  fi   
  eval '[ ${'$3'[$1]+muahaha} ]'  
}
例如。
if ! exists key in array; then echo "No such array element"; fi 

1
@doubleDown 在 if 语句中,如何使用 [ ${array[key]+abc} ] 只有在 [ ${array[key]+abc} ] 不存在时才执行某些操作? - olala
1
当您意外地将枚举数组查询为关联数组时,也无法正常工作。 - Tomáš Zato
2
@duanev:如果没有 +abc,则 [ ${array[key]} ] 将在元素确实被设置但为空值时评估为 false,因此它实际上测试的是值的非空而不是键的存在。 - musiphil
[[ ${array[key]+Y} ]] && echo Y || echo N - Thamme Gowda
3
但是 eval 是有害的!!试试这个例子:exists foo in 'O};cat /etc/passwd;echo -e \\e[5m' - F. Hauri - Give Up GitHub
显示剩余7条评论

77

来自Bash手册,条件表达式:

-v varname
              True if the shell variable varname is set (has been assigned a value).

示例:

declare -A foo
foo[bar]="this is bar"
foo[baz]=""
if [[ -v "foo[bar]" ]] ; then
  echo "foo[bar] is set"
fi
if [[ -v "foo[baz]" ]] ; then
  echo "foo[baz] is set"
fi
if [[ -v "foo[quux]" ]] ; then
  echo "foo[quux] is set"
fi

这将显示foo[bar]和foo[baz]都被设置了(即使后者被设置为空值),而foo[quux]没有被设置。


3
我在匆忙中错过了它;请注意,没有使用典型的数组扩展语法。 - Nathan Chappell
使用 set -u 后,如果字典中不存在 bar,为什么 [[ -v "${foo[bar]}" ]] 会产生未绑定变量错误?如果没有 ${},则可以正常工作;我只是习惯于默认情况下将其用于所有内容。 - bgfvdu3w
3
${foo[bar]} 首先会评估数组变量,因此 [[ -v 命令会测试是否存在名称为该值的变量。 - andysh
这里的问题不在于键是否存在某个值,而是确定该键是否存在就足够了。实际上,这是一个错误的答案,因为“-v”仅在变量名已被设置(已被赋值)时返回“true if”。这超出了此处的要求。 - Anthony Rutledge

25

2023年新答案

很不幸,我不确定在哪个版本的中添加了双方括号,但目前更好的版本是:

array=([12]="red" [51]="black" [129]="blue")

for i in 10 12 30 {50..52} {128..131};do
    if [[ -v array[i] ]];then
        echo "Variable 'array[$i]' is defined"
    else
        echo "Variable 'array[$i]' not exist"
    fi
done

Variable 'array[10]' not exist
Variable 'array[12]' is defined
Variable 'array[30]' not exist
Variable 'array[50]' not exist
Variable 'array[51]' is defined
Variable 'array[52]' not exist
Variable 'array[128]' not exist
Variable 'array[129]' is defined
Variable 'array[130]' not exist
Variable 'array[131]' not exist

请注意测试行中使用的双方括号(并删除引号)。
同样,但使用关联数组的方式:
这与关联数组的使用方式相同:
declare -A aArray=([foo]="bar" [bar]="baz" [baz]=$'Hello world\041')

for i in alpha bar baz dummy foo test;do
    if [[ -v aArray[$i] ]];then
        echo "Variable 'aArray[$i]' is defined"
    else
        echo "Variable 'aArray[$i]' not exist"
    fi
done

Variable 'aArray[alpha]' not exist
Variable 'aArray[bar]' is defined
Variable 'aArray[baz]' is defined
Variable 'aArray[dummy]' not exist
Variable 'aArray[foo]' is defined
Variable 'aArray[test]' not exist
有一点不同:
在常规数组中,方括号([i])中的变量是整数,所以不需要美元符号($),但对于关联数组,由于键是一个单词,需要使用美元符号([$i])!

2021年以前的版本

的4.2版本(及更高版本)开始,内置的test命令有一个新的-v选项。

从4.3版本开始,这个测试可以访问数组的元素。

array=([12]="red" [51]="black" [129]="blue")

for i in 10 12 30 {50..52} {128..131};do
    if [ -v 'array[i]' ];then
        echo "Variable 'array[$i]' is defined"
    else
        echo "Variable 'array[$i]' not exist"
    fi
done

注意:关于ssc的评论,我在-v测试中使用了单引号引用'array[i]',以满足shellcheck的错误SC2208。这在这里似乎并不是真正需要的,因为array[i]中没有通配符字符,无论如何...

旧版 V4.2之前的答案

不幸的是,bash没有办法区分变量和未定义变量。

但有一些方法:

$ array=()
$ array[12]="red"
$ array[51]="black"
$ array[129]="blue"

$ echo ${array[@]}
red black blue

$ echo ${!array[@]}
12 51 129

$ echo "${#array[@]}"
3

$ printf "%s\n" ${!array[@]}|grep -q ^51$ && echo 51 exist
51 exist

$ printf "%s\n" ${!array[@]}|grep -q ^52$ && echo 52 exist

(不给答案)
(对于关联数组,你可以使用相同的方法:)
$ unset array
$ declare -A array
$ array["key1"]="red"
$ array["key2"]="black"
$ array["key3"]="blue"
$ echo ${array[@]}
blue black red

$ echo ${!array[@]}
key3 key2 key1

$ echo ${#array[@]}
3

$ set | grep ^array=
array=([key3]="blue" [key2]="black" [key1]="red" )

$ printf "%s\n" ${!array[@]}|grep -q ^key2$ && echo key2 exist || echo key2 not exist
key2 exist

$ printf "%s\n" ${!array[@]}|grep -q ^key5$ && echo key5 exist || echo key5 not exist
key5 not exist

你可以在不使用外部工具(没有printf | grep,纯粹的bash)的情况下完成这项工作,为什么不构建一个名为checkIfExist()的新的bash函数呢?
$ checkIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) return 0 ;;
        * ) return 1 ;;
      esac";
}

$ checkIfExist array key2 && echo exist || echo don\'t
exist

$ checkIfExist array key5 && echo exist || echo don\'t
don't

或者甚至创建一个新的getIfExist bash函数,如果所需值不存在,则返回所需值并以错误结果代码退出:
$ getIfExist() {
    eval 'local keys=${!'$1'[@]}';
    eval "case '$2' in
        ${keys// /|}) echo \${$1[$2]};return 0 ;;
        * ) return 1 ;;
      esac";
}

$ getIfExist array key1
red
$ echo $?
0

$ # now with an empty defined value
$ array["key4"]=""
$ getIfExist array key4

$ echo $?
0
$ getIfExist array key5
$ echo $?
1

2
在bash-4.2中添加了“-v”,但是直到bash-4.3才添加了检查数组索引的支持。 - mr.spuratic
1
@mr.spuratic 谢谢您的评论! - F. Hauri - Give Up GitHub
5
这整个页面证明了Bash的巨大失败。对于最基本的事情,它充满了20个不符合直觉的方法,并且所有方法都带有像“(不)适用于我的/这个或那个版本”之类的评论。 - Emanuel P
2
谢谢,对我来说在macOS / brew bash 5.1.8上运行得很好 :-) shellcheck 报告了 SC2208 ,针对两个 New answer 代码示例:显然,if 应该使用 [[ ... ]] 而不是 [ ... ] 或者 -v 后面的表达式应该被引用,例如 if [[ -v aArray[$i] ]]if [ -v 'aArray[$i]' ]。我不知道,我通常只是按照 shellcheck 告诉我的去做... - ssc
1
@JohanBoulé 在v4.2.46版本中进行了测试:你是对的,这似乎不起作用。但是checkIfExist函数的第二部分似乎工作正常。否则,您可以尝试在第一个示例中替换if [ -v 'array[i]' ];then ...if ${aArray[$i]+:} false;then ...(不带括号!)。我刚刚在4.2.46(1)-release下测试了这个技巧。 - F. Hauri - Give Up GitHub
显示剩余6条评论

12

那么关于-n测试和:-运算符呢?

比如,这个脚本:

#!/usr/bin/env bash

set -e
set -u

declare -A sample

sample["ABC"]=2
sample["DEF"]=3

if [[ -n "${sample['ABC']:-}" ]]; then
  echo "ABC is set"
fi

if [[ -n "${sample['DEF']:-}" ]]; then
  echo "DEF is set"
fi

if [[ -n "${sample['GHI']:-}" ]]; then
  echo "GHI is set"
fi

输出:

ABC is set
DEF is set

对于这个在bash 4.2下使用set -u工作的解决方案给予极大的赞赏。目前,我正在Red Hat 7上使用Oracle数据库,并安装了bash 4.2。 - Brian Fitzgerald
这应该是被接受的答案!对我有用(bash 4.2.46),而被接受的-v答案则没有。 - freddieknets
@GuyPaddock 你确定在数组变量中包含“:”会给你想要的响应吗?空值或空字符串是数组元素的有效值,我们不想测试它们。我们只想测试索引或键是否存在。“如果包括冒号,操作符将测试参数的存在性和其值不为空”https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameter-Expansion - Anthony Rutledge
1
-n 简单有效。https://github.com/koalaman/shellcheck/wiki/SC2236 - Chirag Arora
1
你说得对,@ChiragArora。当我最初写这个答案时,我不知道那个选项。 - GuyPaddock
显示剩余2条评论

5

在bash 4.3.39(1)-release版本中进行测试

declare -A fmap
fmap['foo']="boo"

key='foo'
# should echo foo is set to 'boo'
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi
key='blah'
# should echo blah is unset in fmap
if [[ -z "${fmap[${key}]}" ]]; then echo "$key is unset in fmap"; else echo "${key} is set to '${fmap[${key}]}'"; fi

当键的值为空字符串时,会出现故障。作为解决方法,可以使用+参数扩展来将空值替换为某个占位符,例如下面的代码:declare -A a[x]=;[[ ${a[x]+_} ]];echo $?可以将空白值转换为下划线,并打印出0,而declare -A a[x]=;[[ ${a[x]} ]];echo $?则输出1 - nisetama

2

重申来自Thamme的观点:

[[ ${array[key]+Y} ]] && echo Y || echo N

这个测试用于检测变量/数组元素是否存在,包括其是否设置为null值。与-v相比,在更广范围的bash版本中使用此方法,并且不会受到set -u等因素的影响。如果您使用此方法出现“bad array subscript”错误,请发表一个示例。


2
在数组和哈希映射中,我发现最简单和直接的解决方案是使用匹配运算符=~

对于数组:

myArray=("red" "black" "blue")
if [[ " ${myArray[@]} " =~ " blue " ]]; then
    echo "blue exists in myArray"
else
    echo "blue does not exist in myArray"
fi

注意:数组周围的空格保证了第一个和最后一个元素可以匹配。值周围的空格保证了精确匹配。

对于哈希映射,实际上是相同的解决方案,因为将哈希映射打印为字符串会给出其值的列表。

declare -A myMap
myMap=(
    ["key1"]="red"
    ["key2"]="black"
    ["key3"]="blue"
)
if [[ " ${myMap[@]} " =~ " blue " ]]; then
   echo "blue exists in myMap"
else
   echo "blue does not exist in myMap"
fi

但是如果你想检查一个哈希映射中是否存在某个键,你可以使用 ! 运算符,这将给出哈希映射中所有的键。

if [[ " ${!myMap[@]} " =~ " key3 " ]]; then
   echo "key3 exists in myMap"
else
   echo "key3 does not exist in myMap"
fi

1

永恒的,一劳永逸的人们。

有一种“清洁代码”的长路,还有一种更简洁、以bash为中心的方法。

$1 = 你正在查找的索引或键。

$2 = 被引用的数组/映射。

function hasKey ()
{
    local -r needle="${1:?}"
    local -nr haystack=${2:?}

    for key in "${!haystack[@]}"; do
        if [[ $key == $needle ]] ;
            return 0
        fi
    done

    return 1
}

一种线性搜索可以被二分查找所替代,对于大数据集表现更佳。首先计数和排序关键字,然后在逐渐接近答案时对干草堆进行经典的二分减半。
现在,对于那些“不,我想要更高效的版本,因为我可能需要处理bash中的大数组”的纯粹主义者,让我们看一下更加面向bash的解决方案,但它仍然保持了清晰的代码和处理数组或映射的灵活性。
function hasKey ()
{
    local -r needle="${1:?}"
    local -nr haystack=${2:?}

    [ -n ${haystack["$needle"]+found} ]
}

这行代码 [ -n ${haystack["$needle"]+found} ] 使用了 bash 变量扩展的形式 ${parameter+word} ,而不是试图测试一个键的值的形式 ${parameter:+word} ,这也不是本文关注的问题。

用法

local -A person=(firstname Anthony lastname Rutledge)

if hasMapKey "firstname" person; then
     # Do something
fi

When not performing substring expansion, using the form described below (e.g., ‘:-’), Bash tests for a parameter that is unset or null. Omitting the colon results in a test only for a parameter that is unset. Put another way, if the colon is included, the operator tests for both parameter’s existence and that its value is not null; if the colon is omitted, the operator tests only for existence.

${parameter:-word}

If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.

${parameter:=word}

If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional

parameters and special parameters may not be assigned to in this way. ${parameter:?word}

If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard

error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted. ${parameter:+word}

If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameter-Expansion

如果$needle不存在,则展开为空,否则展开为非零长度字符串“found”。这将使-n测试成功,如果$needle实际存在(如我所说的“found”),否则失败。

1
这是我找到的脚本最简单的方式。 <search> 是你想要查找的字符串,ASSOC_ARRAY 是保存关联数组的变量名称。
根据你想要实现的目标:
键存在:
if grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key is present; fi

"key exists not":键不存在。
if ! grep -qe "<search>" <(echo "${!ASSOC_ARRAY[@]}"); then echo key not present; fi

"value exists": 值存在
if grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value is present; fi

value exists not:

if ! grep -qe "<search>" <(echo "${ASSOC_ARRAY[@]}"); then echo value not present; fi

1

我编写了一个函数来检查Bash中数组中的键是否存在:

# Check if array key exists
# Usage: array_key_exists $array_name $key
# Returns: 0 = key exists, 1 = key does NOT exist
function array_key_exists() {
    local _array_name="$1"
    local _key="$2"
    local _cmd='echo ${!'$_array_name'[@]}'
    local _array_keys=($(eval $_cmd))
    local _key_exists=$(echo " ${_array_keys[@]} " | grep " $_key " &>/dev/null; echo $?)
    [[ "$_key_exists" = "0" ]] && return 0 || return 1
}

例子

declare -A my_array
my_array['foo']="bar"

if [[ "$(array_key_exists 'my_array' 'foo'; echo $?)" = "0" ]]; then
    echo "OK"
else
    echo "ERROR"
fi

已测试使用GNU bash版本4.1.5(1)-release(i486-pc-linux-gnu)


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