在Bash中,测试数组是否包含某个值的最简单方法是什么?


当前回答

以下是我的看法。

如果可以避免的话,我宁愿不使用bash for循环,因为运行它需要时间。如果有什么东西必须循环,让它是用比shell脚本更低级的语言编写的东西。

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

这是通过创建一个临时关联数组_arr来实现的,它的索引是从输入数组的值派生出来的。(请注意,关联数组在bash 4及以上版本中可用,因此此函数在bash的早期版本中无效。)我们设置$IFS以避免在空格上分词。

该函数不包含显式循环,不过bash内部会遍历输入数组以填充printf。printf格式使用%q来确保输入数据被转义,这样它们就可以安全地用作数组键。

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

注意,这个函数使用的所有东西都是bash内置的,因此没有外部管道拖您的后腿,即使在命令展开中也是如此。

如果你不喜欢使用eval…你可以自由地使用另一种方法。: -)

其他回答

我通常只使用:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

非零值表示找到了匹配。

... 实际上,为了解决它不能与needle1和needle2工作的问题,如果你只想要一个精确匹配,没有更多,没有更少,只需在-o后面添加一个w标志,用于整个单词匹配:

inarray=$(echo ${haystack[@]} | grep -ow "needle" | wc -w)

别胡闹了!使您的解决方案简单、干净和可重用。

这些函数负责索引数组和关联数组。可以通过将搜索算法从线性搜索升级为二进制搜索(用于大型数据集)来改进它们。

##
# Determines if a value exists in an array.
###
function hasArrayValue ()
{
    local -r needle="{$1:?}"
    local -nr haystack="{$2:?}"  # Where you pass by reference to get the entire array in one argument.

    # Linear search. Upgrade to binary search for large datasets.
    for value in "${haystack[@]}"; do
        if [[ "$value" == "$needle" ]]; then
            return 0
        fi
    done

    return 1
}

##
# Determines if a value exists in an associative array / map.
###
function hasMapValue ()
{
    local -r needle="{$1:?}"
    local -nr haystack="{$2:?}"

    # Linear search. Upgrade to binary search for large datasets.
    for value in "${haystack[@]}"; do
        if [[ $value == $needle ]]; then
            return 0
        fi
    done

    return 1
}

是的,同样的逻辑,但在处理bash时,如果函数的名称可以让您知道迭代的对象(或不迭代的对象),则可能(可能)有用。

这是一个小小的贡献:

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

注意:这种方法不区分大小写“两个单词”,但在问题中不需要这样做。

如果你不想重复,这是值得研究的:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

片段改编自:http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ 我认为这很聪明。

编辑: 你可以这样做:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

但后者仅在数组包含唯一值时有效。我认为,在143中寻找1只会给出假阳性。

虽然这里有几个很好的和有用的答案,但我没有找到一个似乎是性能、跨平台和健壮性的正确组合;所以我想分享一下我为我的代码编写的解决方案:

#!/bin/bash

# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item ($1) is contained in an array ($@).
#
# Developer note:
#    The use of a delimiter here leaves something to be desired. The ideal
#    method seems to be to use `grep` with --line-regexp and --null-data, but
#    Mac/BSD grep doesn't support --line-regexp.
function array_contains()
{
    # Extract and remove the needle from $@.
    local needle="$1"
    shift

    # Separates strings in the array for matching. Must be extremely-unlikely
    # to appear in the input array or the needle.
    local delimiter='#!-\8/-!#'

    # Create a string with containing every (delimited) element in the array,
    # and search it for the needle with grep in fixed-string mode.
    if printf "${delimiter}%s${delimiter}" "$@" | \
        grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then
        return 0
    fi

    return 1
}