检查Bash数组中是否存在一个元素。

92
我想知道在Bash中如何高效地检查数组中是否存在一个元素?我找的是类似Python中可以做到的那样的东西,例如:
if element in array:
arr = ['a','b','c','d']

if 'd' in arr:
    do your thing
else:
    do something

我看过使用关联数组来解决Bash 4+的问题的解决方案,但我想知道是否还有其他解决方案。

请理解我知道在数组中进行迭代的平凡解决方案,但我不想用那种方法。


6
不要将“简洁”与“高效”混淆。但是,在 bash 中,没有一种简洁的方式来使用简单数组完成你想要的操作。 - chepner
请查看https://dev59.com/3HA65IYBdhLWcg3wrgsa - Perleone
8个回答

122

你可以这样做:

if [[ " ${arr[*]} " == *" d "* ]]; then
    echo "arr contains d"
fi

如果您搜索的是"a b",那么这将产生错误结果--该子字符串在连接的字符串中,但不作为数组元素。无论您选择什么分隔符,都会出现这种困境。

最安全的方法是循环遍历数组,直到找到该元素:

array_contains () {
    local seeking=$1; shift
    local in=1
    for element; do
        if [[ $element == "$seeking" ]]; then
            in=0
            break
        fi
    done
    return $in
}

arr=(a b c "d e" f g)
array_contains "a b" "${arr[@]}" && echo yes || echo no    # no
array_contains "d e" "${arr[@]}" && echo yes || echo no    # yes

这里是“更干净”的版本,你只需要传递数组名称,而不是所有的元素。

array_contains2 () { 
    local array="$1[@]"
    local seeking=$2
    local in=1
    for element in "${!array}"; do
        if [[ $element == "$seeking" ]]; then
            in=0
            break
        fi
    done
    return $in
}

array_contains2 arr "a b"  && echo yes || echo no    # no
array_contains2 arr "d e"  && echo yes || echo no    # yes
对于关联数组,有一种非常简洁的方法来测试数组是否包含给定的键:使用-v运算符。
$ declare -A arr=( [foo]=bar [baz]=qux )
$ [[ -v arr[foo] ]] && echo yes || echo no
yes
$ [[ -v arr[bar] ]] && echo yes || echo no
no

查看手册中的6.4 Bash 条件表达式


1
是的,Bash可以轻松处理数字值作为字符串。 - glenn jackman
1
在我的实现中,我不得不将"${!array}"更改为"${array[@]}"才能正常工作。我认为"array_contains"和"array_contains2"的建议更安全,因为许多使用grep的人会返回错误的结果。 - Eduardo Lucio
2
考虑引用 $seeking,以避免将其视为模式。 - PesaThe
1
-v 运算符仅在 Bash 4.3+ 中有效。在早期版本中,使用 +_ 测试键:declare -A says says[horse]=neigh says[fish]= [[ ${says[horse]+_} ]] && echo "horse: ${says[horse]}" || echo "horse doesn't exist" horse: neigh [[ ${says[fish]+_} ]] && echo "fish: ${says[fish]}" || echo "fish doesn't exist" fish: [[ ${says[unicorn]+_} ]] && echo "unicorn: ${says[unicorn]}" || echo "unicorn doesn't exist"在 v4.2.46 中测试。来源 - Joseph Collins
@JosephCollins,我看到你的编辑了。我鼓励你贡献一个新答案。在完成后@我,我会为它投票。 - glenn jackman
显示剩余4条评论

38

除了显而易见的警告之外,如果您的数组实际上像上面那样,您可以执行以下操作:

if [[ ${arr[*]} =~ d ]]
then
  do your thing
else
  do something
fi

4
感谢您对教授新内容的支持。但是,d也会匹配xdy - Olaf Dietsche
3
这可以通过编写if [[ ${arr[*]} =~ $(echo '\<d\>') ]]来解决。 - Stefan van den Akker
谢谢,这很有帮助。但是如果后面没有else的话,我该如何否定结果呢?这可以节省一些冗余的代码。@Steven Penny - Sven M.
1
@SvenM 如果 [[ ! ${arr[*]} =~ d ]]? - rwenz3l

29
  1. 初始化数组arr并添加元素
  2. 设置要搜索的变量SEARCH_STRING
  3. 检查您的数组是否包含元素
    arr=()
    arr+=('a')
    arr+=('b')
    arr+=('c')
   
    SEARCH_STRING='b'

    if [[ " ${arr[*]} " == *"$SEARCH_STRING"* ]];
    then
        echo "YES, your arr contains $SEARCH_STRING"
    else
        echo "NO, your arr does not contain $SEARCH_STRING"
    fi

3
这是这个帖子中唯一对我有效的回复,谢谢! - Sishaar Rao
这是非常简洁的,我能够与BASH_ARGV一起使用它来测试是否作为参数给出了某个单词,需要不同的一次性设置是否存在,而无需进行某种过度的argparse - dragon788
这是我在 StackOverflow 上找到的唯一有效的答案。 - Rehmat
一个博客文章几乎有这个功能,但在 $SEARCH_STRING 外部有空格,这并不起作用。我必须删除这些空格(如果我删除左侧引号内部的空格也可以运行)。 - zsalya

22
如果数组元素不包含空格,另一个(可能更易读)的解决方案是:
if echo ${arr[@]} | grep -q -w "d"; then 
    echo "is in array"
else 
    echo "is not in array"
fi

+1。好答案。这个解决方案适用于我检查数字是否存在于数字数组中。而@Steven Penny的解决方案在我的数字数组中并不完全正确。如果echo ${combined_p2_idx[@]} | grep -q -w $subset_id,则执行以下操作。 - Good Will
我同意@GoodWill的观点。这应该是被采纳的答案。其他选项不够强大。 - Grr
如果数组中有负数,它将会出现错误。 - jessi
添加“--”以表示参数结束,负数不会被视为grep的参数。 - drAlberT
1
到目前为止,最优雅和易懂的解决方案。对我来说非常有效。 - Lucas Lima

4

由于bash没有内置的inarray操作符,而=~操作符或[[ "${array[@]" == *"${item}"* ]]记法容易让我感到困惑,因此我通常将grep与此处字符串组合使用:

colors=('black' 'blue' 'light green')
if grep -q 'black' <<< "${colors[@]}"
then
    echo 'match'
fi

请注意:这里存在与其他答案相同的假阳性问题,即当要搜索的项完全包含在另一项中,但两者不相等时发生。

if grep -q 'green' <<< "${colors[@]}"
then
    echo 'should not match, but does'
fi

如果这是您使用情况的问题,那么您可能绕不过遍历数组:

for color in "${colors[@]}"
do
    if [ "${color}" = 'green' ]
    then
        echo "should not match and won't"
        break
    fi
done

for color in "${colors[@]}"
do
    if [ "${color}" = 'light green' ]
    then
        echo 'match'
        break
    fi
done

如上面@Querty的答案所示,为了避免误报,只需在grep命令中添加“-x”参数以匹配整行即可。 - ldeck

3
array=("word" "two words") # let's look for "two words"

使用 grepprintf

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

使用 for

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

对于未找到的结果,请添加 || <run_your_if_notfound_command_here>

@Querty 我不知道为什么这不是最佳答案。grep -x... 使其使用起来最简单。 - ldeck

2
这里提供一种可能比遍历更快的方式,就是将数组转换为字符串,截断它,然后获取新数组的大小。不能确定效率是否更高。例如,要查找“d”的索引:
arr=(a b c d)    
temp=`echo ${arr[@]}`
temp=( ${temp%%d*} )
index=${#temp[@]}

您可以将其转换为类似以下的函数:

get-index() {

    Item=$1
    Array="$2[@]"

    ArgArray=( ${!Array} )
    NewArray=( ${!Array%%${Item}*} )

    Index=${#NewArray[@]}

    [[ ${#ArgArray[@]} == ${#NewArray[@]} ]] && echo -1 || echo $Index

}

您可以随后调用:

get-index d arr

它将会回显3,可以通过以下方式进行赋值:

index=`get-index d arr`

0

顺便提一下,这是我用的:

expr "${arr[*]}" : ".*\<$item\>"

这个代码只适用于数组项和搜索目标中没有分隔符的情况。我的应用程序不需要解决一般情况。


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