Easiest way to check for an index or a key in an array?

Using:

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?

111741 次浏览

新答案

版本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
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

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

联合数组联合数组的工作方式相同:

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

有一点不同: < br > 在常规数组中,括号之间的变量([i])是整数,因此不需要美元符号($) ,但对于关联数组,因为 钥匙是一个单词,所以需要 $([$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 作为 纯粹的狂欢)的情况下完成这项工作,为什么不呢,将 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

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

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

基本上 ${array[key]+abc}所做的就是

  • 如果设置了 array[key],返回 abc
  • 如果没有设置 array[key],则不返回任何内容

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

如果省略冒号,则操作符仅测试是否存在[ 参数]

  1. 这个答案实际上是改编自这个 SO 问题的答案: 如何判断 bash shell 脚本中是否定义了字符串

包装函式:

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

在 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

这是我找到的最简单的写剧本的方法。

<search> 是您想要查找的字符串,ASSOC_ARRAY是保存您的关联数组的变量的名称。

取决于你想达到的目标:

键存在 :

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

键不存在 :

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

值存在 :

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

值不存在 :

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

我编写了一个函数来检查 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

使用 GNUbash 测试,版本4.1.5(1)-发行版(i486-pc-linux-GNU)

男人狂欢开始,条件表达式:

-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 ]没有被设置。

那么 -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

当我检查的键没有设置时,我会得到 bad array subscript错误。因此,我写了一个循环遍历键的函数:

#!/usr/bin/env bash
declare -A helpList


function get_help(){
target="$1"


for key in "${!helpList[@]}";do
if [[ "$key" == "$target" ]];then
echo "${helpList["$target"]}"
return;
fi
done
}


targetValue="$(get_help command_name)"
if [[ -z "$targetvalue" ]];then
echo "command_name is not set"
fi

它在找到时回显该值,在找不到时不回显任何值。我试过的所有其他解决方案都给了我这个错误。

来自 Thamme 的重申:

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

这将测试变量/数组元素是否存在,包括它是否被设置为空值。这适用于比 -v 更广泛的 bash 版本,并且对 set-u 之类的东西似乎不敏感。如果你看到一个“坏数组下标”使用此方法请发布一个例子。

永远的人们,一劳永逸。

“干净的代码”还有很长的路要走,还有一种更短、更简洁、以 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

当不执行子字符串扩展时,使用所述形式 在下面(例如,‘ :-’) ,Bash 测试未设置或空的参数。 省略冒号只会导致测试的参数为 换句话说,如果包含冒号,则运算符测试 参数的存在及其值不为空; 如果 如果省略冒号,则操作符仅测试是否存在。

${参数:-word }

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

${参数: = word }

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

参数和特殊参数不能以这种方式指定。 ${参数: ? 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

如果该 shell 不是交互式的,则退出 ${参数: + 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”。如果 $needle确实存在(正如我所说的“发现”) ,这将使 -n测试成功,否则将失败。

在数组和散列映射的情况下,我发现最简单和更直接的解决方案是使用匹配操作符 =~

对于数组:

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