【问题标题】:Easiest way to check for an index or a key in an array?检查数组中的索引或键的最简单方法?
【发布时间】:2012-11-04 14:57:04
【问题描述】:

使用:

set -o nounset
  1. 有一个像这样的索引数组:

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

    检查元素 1 是否设置的最短方法是什么?
    我有时会使用以下内容:

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

    我想知道是否有首选。

  2. 如何处理不连续的索引?

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

    例如,如何快速检查51 是否已设置?

  3. 如何处理关联数组?

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

    例如,如何快速检查key2 是否已被使用?

【问题讨论】:

    标签: arrays bash indexing key


    【解决方案1】:

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

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

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

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

    参考:
    1. 请参阅 Bash 手册中的 Parameter Expansion 和小注释

    如果省略冒号,则运算符仅测试 [of parameter]

    1. 这个答案实际上改编自这个 SO 问题的答案:How to tell if a string is not defined in a bash shell script?

    包装函数:

    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 
    

    【讨论】:

    • 我是这样解决的:if test "${myArray['key_or_index']+isset}";然后回显“是”;否则回显“否”;菲;在我看来,这是最简单的方法,它适用于索引数组和关联数组。谢谢
    • @doubleDown 如何在 if 子句中使用 [ ${array[key]+abc} ] 以仅在 [ ${array[key]+abc} ] 不存在的情况下执行某些操作?
    • 当您不小心将枚举数组查询为关联数组时也不起作用。
    • @duanev:如果没有+abc[ ${array[key]} ] 将在元素确实设置但为空值时评估为 false,因此它实际上是在测试值非空而不是键是否存在。
    • 但是eval 是邪恶的!!试试这个:exists foo in 'O};cat /etc/passwd;echo -e \\e[5m' 示例!!
    【解决方案2】:

    来自man 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] 未设置。

    【讨论】:

    • 看了一眼就错过了;注意没有使用典型的数组扩展语法。
    • 对于set -u,如果字典中不存在bar,为什么[[ -v "${foo[bar]}" ]] 会产生未绑定变量错误?没有${} 也能正常工作;我只是习惯于默认使用它。
    • "${foo[bar]}" 首先评估数组变量,因此[[ -v 命令测试具有该值名称的变量
    • key 值的存在与否不是这里的问题。确定密钥是否存在就足够了。这实际上是一个错误的答案,因为-v 仅返回“true if 已设置变量名(已分配值”。这超出了此处的要求。
    【解决方案3】:

    新答案

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

    从 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's comment,我在-v 测试中引用了'array[i]',以满足shellcheck 的错误 SC2208。这似乎不是真的需要,因为array[i] 中没有 glob 字符,反正...

    这与 关联数组 的工作方式相同:

    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])是整数,所以不需要美元符号($),但是对于关联数组,因为 key 是一个单词,所以需要$ ([$i])!

    在 V4.2 之前的旧答案

    不幸的是,bash 无法区分 emptyundefined 变量。

    但是有一些方法:

    $ 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 作为 pure 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
    

    【讨论】:

    • 赞成投票:这个答案是在bash 的 V4.2 之前发布的!答案已编辑!
    • -v 已添加到 bash-4.2 BUT 直到 bash-4.3 才添加对检查数组索引的支持。
    • @mr.spuratic 感谢您的评论!
    • 整个页面都证明了 bash 的巨大失败。对于最基本的东西,它充满了 20 种违反直觉的方法,并且都带有诸如“(不)对我/这个或那个版本工作”之类的 cmets。
    • 谢谢,在 macOS / brew bash 5.1.8 上非常适合我 :-) shellcheck 报告 SC2208 两个 新答案 代码示例:显然,@ 987654346@ 应该使用[[ ... ]] 而不是[ ... ] 或者-v 之后的表达式应该被引用,例如if [[ -v aArray[$i] ]]if [ -v 'aArray[$i]' ]。打败我,我通常只是按照shellcheck 告诉我的...
    【解决方案4】:

    -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。
    • 这应该是公认的答案!为我工作(bash 4.2.46),而接受的 -v 答案没有。
    • 你是对的,@ChiragArora。当我最初写这个答案时,我不知道这个选项。
    【解决方案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 $? 打印1,但declare -A a[x]=;[[ ${a[x]+_} ]];echo $? 打印0
    【解决方案6】:

    从 Thamme 重申这一点:

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

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

    【讨论】:

      【解决方案7】:

      这是我为脚本找到的最简单的方法。

      <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
      

      【讨论】:

        【解决方案8】:

        我写了一个函数来检查一个键是否存在于 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)

        【讨论】:

          【解决方案9】:

          为所有时间的人,一劳永逸。

          有一个“干净的代码”很长的路要走,还有一个更短、更简洁、以 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} 形式,它也尝试测试键的值,这不是手头的问题 em>。

          用法

          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.
          

          ${参数:=字}

          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 不存在扩展为空,否则扩展为非零长度字符串,“找到”。这将使-n 测试成功,如果$needle 确实存在(正如我所说的“找到”),否则失败。

          【讨论】:

            【解决方案10】:

            当我检查的密钥未设置时,我收到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
            

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

            【讨论】:

              猜你喜欢
              • 2012-10-24
              • 1970-01-01
              • 2014-09-03
              • 1970-01-01
              • 1970-01-01
              • 2013-12-20
              • 1970-01-01
              • 2019-11-29
              • 1970-01-01
              相关资源
              最近更新 更多