在Bash数组中获取某个值的索引

91

我在 bash 中有类似以下代码

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

我想做类似的事情

echo ${myArray['green']}

在这种情况下,输出将为2。这可行吗?

1
我的第一个建议是在脚本编程时使用Python而不是Bash。自从我开始使用Python后,我就再也没有回头过。 - Steve Walsh
14
谢谢,我知道用 Python 字典很容易实现,但那不是我想要的。 - user137369
21个回答

111

这将完成它:

#!/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()),那么你可以使它更加通用。

3
这不适用于稀疏数组或关联数组。为了使其适用于所有类型的数组,请将C风格的for循环替换为:for i in "${!my_array[@]}"; do。此外,如果您只想查找第一个索引,则可以在找到索引后使用break来终止循环。 - gniourf_gniourf
4
${!my_array[@]}中,!表示反转或否定操作符。它用于返回数组my_array中所有键的列表。 - bodacydo
2
@bodacydo,我也曾好奇 ! 是什么意思,后来尝试了一下,发现它不是列出数组中的所有值,而是从零开始列出索引。 - Fredrick Gauss
6
! 表示返回元素的索引而不是元素的值。 - DocSalvager
1
为什么在echo后面要加分号?这不是多余的吗? - Lou
是的,在Bash中,语句后面不需要分号。对于大多数语句来说,换行符表示语句的结束。但为了代码清晰和易读性,您可以自行选择。 - Kukuster

38

在使用前必须声明数组

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

这是创建关联数组的一种方式。很好。 - David Okwii
在GNU bash版本4.1.2(1)-release (x86_64-redhat-linux-gnu)中无法工作。 - mario ruiz
我无法评论4.1.2版本(4.1版本发布于2009年),但在我的当前Bash(Ubuntu 16.04,Bash 4.3.42)中,它仍然按照广告所述正常工作。 - Olaf Dietsche
12
OP 希望得到索引而不是值。 - Ura
2
@Dejan 是的,这在标题中已经提到了。但是请看一下问题,看看 OP 想要实现什么。 - Olaf Dietsche

23

还有一种巧妙的方法:

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

你会得到2个。 这里是参考链接


3
巧妙的解决方案。我没有将这个更简单的方案标记为接受(尽管我给它点了赞)的原因是它稍微容易出错一些。虽然当前已接受的答案在输入无效颜色时不会输出任何内容,但这个解决方案将会输入数组中元素的数量加一(所以是 3,根据我的示例数组),这可能会更难调试。 - user137369

15
不行。在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)。 - mario ruiz
1
关联数组需要显式声明;我是在2013年知道这个还是在回答中忘记了,这是有争议的 :) - chepner
不幸的是,这似乎是正确的。建议迭代数组(或让某些命令行工具为您完成)的答案忽略了重点。为了获得O(1)的反向查找,似乎需要声明两个关联数组:一个用于键->值,另一个用于值->键。所有插入都应通过包装函数进行,该函数将它们添加到两个数组中。 - Alex Jansen

10

更加简洁,适用于Bash 3.x:

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

for i in "${!my_array[@]}"; do
   [[ "${my_array[$i]}" = "${value}" ]] && break
done

echo $i

这似乎是@SteveWalsh五年前(现在是十年前)的答案,但有个问题,它会声称值是数组中的最后一个元素,但实际上并不在数组中。这个答案应该被删除或编辑,将&& break替换为echo $i && break - undefined

6

另一个棘手的一行代码:

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"

查找匹配项:

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

    cut -f1 -d:

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

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

由于行号是从1开始计数的,而数组是从0开始计数的,因此需要减去1。

  • 如果$(...)没有匹配:

    • 则返回默认值010#0),并且不返回任何内容
  • 如果$(...)匹配:
    • 一个行号存在,并带有前缀10#0;例如:10#0210#0910#014
    • 前缀10#将强制使用十进制数字而非八进制数字


使用awk而不是grepcut和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中的双引号替换为转义版本
  • 由于我们需要变量替换,因此此段代码需要进行更多的转义

我喜欢awk的解决方案,另一个则对于我来说有太多的bash黑魔法:P...相信使用printf "%s\n" "${arr[@]}" | [...]IFS=$'\n' [...] <<< "${arr[*]}"更具可移植性和鼓励性。此外,值得注意的是awk解决方案将打印每个匹配索引。要仅返回第一个/最左边的索引,可以在打印后的块中调用awk函数exit(即awk "... {print NR-1; exit}")。要返回最后一个/最右边的索引,请将NR-1存储在变量中并在END处打印:awk "... {result = NR-1} END {print result}" - Joshua Skrzypek

3
这可能仅适用于数组,
my_array=(red orange green)
echo "$(printf "%s\n" "${my_array[@]}")" | grep -n '^orange$' | sed 's/:orange//'

输出:

2

如果你想要在一个tsv文件中找到header的索引位置,

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

2

这只是初始化关联数组的另一种方法,就像chepner展示的那样。 不要忘记你需要显式地使用-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

2

如果你需要反复查找数组中的值,可以通过关联数组构建一个反向索引。

my_array=(red orange green)
declare -A my_ass_arr

for i in ${!my_array[@]}; do my_ass_arr[${my_array[$i]}]=$i; done

this_val="green"
echo ${my_ass_arr[$this_val]}

这样你只需要遍历数组一次。我不确定bash在关联数组的索引和搜索方面的底层操作,但这可能比每次进行蛮力搜索更快。


2
在zsh中,您可以这样做:
xs=( foo bar qux )
echo ${xs[(ie)bar]}

请参见zshparam(1)中的Subscript Flags子部分。

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