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


当前回答

我通常只使用:

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

非零值表示找到了匹配。

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

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

其他回答

这对我来说很管用:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

示例调用:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi

以下是我的看法。

如果可以避免的话,我宁愿不使用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…你可以自由地使用另一种方法。: -)

回答完之后,我读到了另一个我特别喜欢的答案,但它有缺陷,遭到了不好评。我受到了启发,这里有两种可行的新方法。

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

使用grep和printf:

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

使用:

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

对于not_found结果,添加|| <run_your_if_notfound_command_here>

如果你需要性能,你不希望每次搜索时都要遍历整个数组。

在这种情况下,您可以创建一个表示该数组索引的关联数组(哈希表或字典)。也就是说,它将每个数组元素映射到它在数组中的索引:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

然后你可以这样使用它:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

并像这样测试成员:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

或者:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

请注意,即使在测试值或数组值中存在空格,该解决方案也能正确执行。

作为奖励,您还可以通过以下方式获得数组中值的索引:

echo "<< ${myarray_index[$member]} >> is the index of $member"
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

如果你喜欢,你可以使用相同的长选项:

--fixed-strings --quiet --line-regexp --null-data