【问题标题】:Get the index of a value in a Bash array获取 Bash 数组中某个值的索引
【发布时间】:2013-02-08 07:55:00
【问题描述】:

我在bash 有东西喜欢

myArray=('red' 'orange' 'green')

我想做类似的事情

echo ${myArray['green']}

在这种情况下会输出2。这可以实现吗?

【问题讨论】:

  • 我的第一个建议是使用 python 而不是 bash 来编写脚本。一旦我开始这样做,我就再也没有回头。
  • 谢谢,我知道我可以用 python 字典轻松完成,但这不是我想要的。

标签: arrays bash indexing


【解决方案1】:

没有。您只能在bash 中使用整数索引一个简单数组。关联数组(在bash 4 中引入)可以通过字符串进行索引。但是,如果没有专门构造的关联数组,它们不会提供您所要求的反向查找类型。

$ declare -A myArray
$ myArray=([red]=0 [orange]=1 [green]=2)
$ echo ${myArray[green]}
2

【讨论】:

  • 尽管有争论,但总是打印 2... 红色和橙色打印 2. GNU bash,版本 4.1.2(1)-release (x86_64-redhat-linux-gnu)
  • 关联数组需要显式声明;我是否在 2013 年就知道,或者只是在这个答案中忘记这样做,还有待商榷:)
  • 不幸的是,这似乎是正确的。建议迭代数组(或让一些命令行工具为您完成)的答案没有抓住重点。要获得 O(1) 反向查找,您似乎需要声明两个关联数组:一个用于 key->value,一个用于 value->key。然后所有的插入都应该通过一个将它们添加到两个数组中的包装函数进行。
【解决方案2】:

这样就可以了:

#!/bin/bash

my_array=(red orange green)
value='green'

for i in "${!my_array[@]}"; do
   if [[ "${my_array[$i]}" = "${value}" ]]; then
       echo "${i}";
   fi
done

显然,如果你把它变成一个函数(例如 get_index() ) - 你可以让它通用

【讨论】:

  • 这不适用于稀疏数组或关联数组。要让它适用于所有类型的数组,请将 C 风格的 for 循环替换为:for i in "${!my_array[@]}"; do。此外,如果您只想查找第一个索引,则可能需要 break 如果找到索引则循环。
  • 您能解释一下! 在${!my_array[@]} 中的含义吗?
  • @bodacydo,我也想知道!的意思,然后试了一下,发现不是列出数组中的所有值,而是列出从零开始的索引。
  • ! 表示返回元素的索引而不是元素的值。
  • 为什么echo后面有分号?这不是没必要吗?
【解决方案3】:

你必须在使用前声明你的数组

declare -A myArray
myArray=([red]=1 [orange]=2 [green]=3)
echo ${myArray['orange']}

【讨论】:

  • 这是创建关联数组的某种方式。不错。
  • 不适用于 GNU bash,版本 4.1.2(1)-release (x86_64-redhat-linux-gnu)
  • 我无法评论 4.1.2(4.1 来自 2009 年),但在我当前的 Bash(Ubuntu 16.04、Bash 4.3.42)上它仍然像宣传的那样工作。
  • OP 想要一个索引而不是一个值。
  • @Dejan 是的,这在标题中。但也要看看问题,看看 OP 想要实现什么。
【解决方案4】:

还有一个棘手的方法:

echo ${myArray[@]/green//} | cut -d/ -f1 | wc -w | tr -d ' '

然后你得到 2 Here are references

【讨论】:

  • 巧妙的解决方案。我没有将这个更简单的方法标记为已接受(尽管我赞成)的唯一原因是它更容易出错。虽然currently accepted answer 在输入无效颜色时不会echo,但此解决方案将输入数组中元素的数量加一(因此3,遵循我的示例数组),这可能有点难以调试。
【解决方案5】:

我喜欢这个解决方案:

let "n=(`echo ${myArray[@]} | tr -s " " "\n" | grep -n "green" | cut -d":" -f 1`)-1"

变量 n 将包含结果!

【讨论】:

    【解决方案6】:

    这只是 chepner 所示的另一种初始化关联数组的方法。 不要忘记您需要显式 declare 或使用 -A 属性键入关联数组。

    i=0; declare -A myArray=( [red]=$((i++)) [orange]=$((i++)) [green]=$((i++)) )
    echo ${myArray[green]}
    2
    

    这消除了对值进行硬编码的需要,并使您不太可能最终得到重复。

    如果您有很多值要添加,将它们放在单独的行上可能会有所帮助。

    i=0; declare -A myArray; 
    myArray+=( [red]=$((i++)) )
    myArray+=( [orange]=$((i++)) )
    myArray+=( [green]=$((i++)) )
    echo ${myArray[green]}
    2
    

    假设您想要一组数字和小写字母(例如:用于菜单选择),您也可以这样做。

    declare -a mKeys_1=( {{0..9},{a..z}} );
    i=0; declare -A mKeys_1_Lookup; eval mKeys_1_Lookup[{{0..9},{a..z}}]="$((i++))";
    

    如果你然后运行

    echo "${mKeys_1[15]}"
    f
    echo "${mKeys_1_Lookup[f]}"
    15
    

    【讨论】:

      【解决方案7】:

      这可能只适用于数组,

      my_array=(red orange green)
      echo "$(printf "%s\n" "${my_array[@]}")" | grep -n '^orange$' | sed 's/:orange//'
      

      输出:

      2
      

      如果要在 tsv 文件中查找标头索引,

      head -n 1 tsv_filename | sed 's/\t/\n/g' | grep -n '^header_name$' | sed 's/:header_name//g'
      

      【讨论】:

        【解决方案8】:

        在zsh中你可以这样做

        xs=( foo bar qux )
        echo ${xs[(ie)bar]}
        

        参见 zshparam(1) 小节下标标志

        【讨论】:

          【解决方案9】:

          在 Bash 3.x 中更简洁且有效:

          my_array=(red orange green)
          value='green'
          
          for i in "${!my_array[@]}"; do
             [[ "${my_array[$i]}" = "${value}" ]] && break
          done
          
          echo $i
          

          【讨论】:

            【解决方案10】:

            另一个棘手的单行:

            index=$((-1 + 10#0$(IFS=$'\n' echo "${my_array[*]}" | grep --line-number --fixed-strings -- "$value" | cut -f1 -d:)))
            

            特点:

            • 支持带空格的元素
            • 找不到时返回-1

            注意事项:

            • 要求value 不为空
            • 阅读困难

            按执行顺序分解解释:

            IFS=$'\n' echo "${my_array[*]}"
            

            将数组扩展分隔符 (IFS) 设置为新行字符并扩展数组

            grep --line-number --fixed-strings -- "$value"
            

            grep 匹配:

            • 显示行号(--line-number 或 -n)
            • 使用固定字符串(--fixed-strings 或 -F;禁用正则表达式)
            • 允许以- (--) 开头的元素

              剪切-f1 -d:

            只提取行号(格式为<line_num>:<matched line>)

            $((-1 + 10#0$(...)))
            

            减去 1,因为行号是 1-indexed 而数组是 0-indexed

            • 如果$(...) 不匹配:

              • 不返回任何内容并使用默认值0 (10#0)
            • 如果$(...) 匹配:
              • 存在行号&以10#0为前缀;即10#02、10#09、10#014等
              • 10# 前缀强制使用 base-10/十进制数字而不是八进制数


            使用awk 代替grep、cut 和bash 算术:

            IFS=$'\n'; awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}" <<< "${my_array[*]}"
            

            特点:

            • 支持带空格的元素
            • 支持空元素
            • 在子 shell 中打开的命令更少

            注意事项:

            • 找不到时返回

            按执行顺序分解解释:

            IFS=$'\n' [...] <<< "${my_array[*]}"
            

            将数组扩展分隔符 (IFS) 设置为新行字符并扩展数组

            awk "\$0 == \"${value//\"/\\\"}\" {print NR-1}"
            

            匹配整行并打印以 0 为索引的行号

            • ${value//\"/\\\"} 用转义版本替换 $value 中的双引号
            • 因为我们需要变量替换,所以这个段的转义比想要的要多

            【讨论】:

              【解决方案11】:

              简单的解决方案:

              my_array=(red orange green)
              echo ${my_array[*]} | tr ' ' '\n' | awk '/green/ {print NR-1}'
              

              【讨论】:

              • 这也会触发 包含 green 的单词,这不是预期的结果(完全匹配)。
              【解决方案12】:

              这会输出查询的从 0 开始的数组索引(此处为“橙色”)。

              echo $(( $(printf "%s\n" "${myArray[@]}" | sed -n '/^orange$/{=;q}') - 1 ))
              

              如果查询没有出现在数组中,那么上面的输出-1。

              如果查询在数组中出现多次,则上面输出查询第一次出现的索引。

              由于这个解决方案调用了 sed,我怀疑它在效率上能否与这个线程中的一些纯 bash 解决方案竞争。

              【讨论】:

              • 你不需要tr。只需执行printf "%s\n" "${myArray[@]}"。看来您对 Bash 不是很满意,所以这就是您发现解决方案更容易的原因。你应该学习 Bash 习语,然后你就再也不会写这些恐怖了:)。
              • 感谢@gniourf_gniourf,这绝对是一个进步。我已经更新,使用printf 而不是tr。希望现在只有一半可怕。 :)
              • 它看起来有点奇怪,但实际上并没有那么糟糕:它实际上适用于包含空格的元素(但仍然不如其他一些答案那么健壮)。
              • 我同意这是可读的,但是基于 1 的索引而 bash 数组是基于 0 的索引可能会在更大的脚本上造成混淆。
              • @user137369 我同意。我现在已经修改了我的解决方案以输出基于 0 的索引。当查询未出现在数组中时,这自然会产生-1 的输出。
              【解决方案13】:

              这显示了一些返回数组成员索引的方法。 该数组对第一个和最后一个索引使用不适用的值, 提供从 1 开始的索引,并提供限制。

              while 循环是一种有趣的迭代方法,带有截止, 目的是为数组值生成索引, 循环体只包含一个用于空操作的冒号。 重要的部分是 i 的迭代直到匹配, 或过去可能的匹配项。

              函数 indexof() 会将文本值转换为索引。 如果一个值不匹配,该函数返回一个错误代码,可以 在测试中用于执行错误处理。 与数组不匹配的输入值将超出范围限制(-gt, -lt) 测试。

              有一个循环好/坏值的测试(主代码),前 3 行被注释掉,但尝试一些变化来查看有趣的结果 (lines 1,3 or 2,3 or 4)。我包含了一些考虑错误条件的代码,因为它很有用。

              最后一行代码调用 indexof 函数,其值为“green”,这将回显索引值。

              indexof(){
                local s i;
              
                #   0    1   2     3    4
                s=( @@@ red green blue @o@ )
              
                while [ ${s[i++]} != $1 ] && [ $i -lt ${#s[@]} ]; do :; done
              
                [ $i -gt 1 ] && [ $i -lt ${#s[@]} ] || return
              
                let i--
              
                echo $i
              };# end function indexof
              
              # --- main code ---
              echo -e \\033c
              echo 'Testing good and bad variables:'
              for x in @@@ red pot green blue frog bob @o@;
              do
                #v=$(indexof $x) || break
                #v=$(indexof $x) || continue
                #echo $v
                v=$(indexof $x) && echo -e "$x:\t ok" || echo -e "$x:\t unmatched"
              done 
              
              echo -e '\nShow the index of array member green:'
              indexof green
              

              【讨论】:

                【解决方案14】:
                myArray=('red' 'orange' 'green')
                echo ${myArray[@]}
                arrayElementToBeRemoved='orange'
                echo "removing element: $arrayElementToBeRemoved"
                # Find index of the array element (to be kept or preserved)
                let "index=(`echo ${myArray[@]} | tr -s " " "\n" | grep -n "$arrayElementToBeRemoved" | cut -d":" -f 1`)-1"
                unset "myArray[$index]"
                echo ${myArray[@]}
                

                【讨论】:

                • tr 对于包含空格的数组元素将失败。请改用printf "%s\n" "${myArray[@]}"。您还应该使用egrep 和^pattern$,否则它将匹配子字符串。
                【解决方案15】:

                我自己也想要类似的东西并避免循环,想出了...

                myArray=('red' 'orange' 'green')
                declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"green\".*,\1,p"
                

                ...如果找不到元素,则标准输出不会受到污染...

                $ myArray=('red' 'orange' 'green')
                $ declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"green\".*,\1,p"
                2
                
                $ declare -p myArray | sed -n "s,.*\[\([^]]*\)\]=\"gren\".*,\1,p"
                $
                

                之后我用谷歌搜索,发现了这个问题,并认为我会分享;)

                【讨论】:

                • 巧妙的解决方案,但如果其中一个选项包含双引号,则很难阅读并且会出现问题。此外,以这种方式解析文本很挑剔,我不相信它会无限期地工作(认为它可能会)。最后,typeset 甚至被 macOS 中的古老 Bash 标记为已过时;应该改用declare。
                • TFT @user137369 - 双引号观察是个好地方 - TBH,我什至从未考虑过,因为没有建议它们,我不需要考虑它们供我自己使用 :) 我确实知道 declare(1),但 obvs 没有意识到 typeset(1) 已经“消亡”了。
                【解决方案16】:

                这个输出字符“Molly”的基于 1 的 NEUROMANCER 索引;)

                get_index() {
                
                  declare -n dummy_array="$1"
                  # alternative: split read -ra array <<< "${dummy_array[@]}"
                  local array=( "${dummy_array[@]}" )
                  # alternative: local value; value="$( for dummy_value; do true; done; echo "$dummy_value" )"
                  local value=$2
                  local length="${#array[@]}"
                  local i=0
                  
                  while (( i < length ))
                  do
                    if [ "${array[$i]}" = "$value" ]
                    then echo $(( i + 1 )); return 0
                    fi; (( i++ ))
                  done
                  
                  echo "$2 not found beneath $1"
                  exit 1
                
                }
                
                NEUROMANCER=(Case Molly Riviera)
                get_index NEUROMANCER Molly
                get_index NEUROMANCER 'John Doe'
                

                如果你随后运行:

                $ bash script.sh
                2
                John Doe not found beneath NEUROMANCER
                
                

                【讨论】:

                  【解决方案17】:
                  function array_indexof() {
                    [ $# -lt 2 ] && return 1
                    local a=("$@")
                    local v="${a[-1]}"
                    unset a[-1]
                    local i
                    for i in ${!a[@]}; do
                      if [ "${a[$i]}" = "$v" ]; then
                        echo $i
                        return 0 # stop after first match
                      fi
                    done
                    return 1
                  }
                  
                  a=(a b c d)
                  i=$(array_indexof "${a[@]}" d)
                  echo $i # 3
                  

                  【讨论】:

                    猜你喜欢
                    • 2011-02-26
                    • 2017-11-08
                    • 1970-01-01
                    • 2022-01-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多