在Bash中,测试数组是否包含某个值的最简单方法是什么?
当前回答
如果你需要性能,你不希望每次搜索时都要遍历整个数组。
在这种情况下,您可以创建一个表示该数组索引的关联数组(哈希表或字典)。也就是说,它将每个数组元素映射到它在数组中的索引:
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"
其他回答
这是一个小小的贡献:
array=(word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
注意:这种方法不区分大小写“两个单词”,但在问题中不需要这样做。
以下是我的看法。
如果可以避免的话,我宁愿不使用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…你可以自由地使用另一种方法。: -)
如果您想做一个快速而复杂的测试,看看是否值得遍历整个数组以获得精确匹配,Bash可以像对待标量一样对待数组。测试标量中的匹配项,如果没有,则跳过循环节省时间。显然你会得到假阳性。
array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
echo "Checking"
for element in "${array[@]}"
do
if [[ $element == "words" ]]
then
echo "Match"
fi
done
fi
这将输出“Checking”和“Match”。使用array=(word "two words" something),它只会输出"Checking"。使用array=(单词“two widgets”什么的)将没有输出。
如果你需要性能,你不希望每次搜索时都要遍历整个数组。
在这种情况下,您可以创建一个表示该数组索引的关联数组(哈希表或字典)。也就是说,它将每个数组元素映射到它在数组中的索引:
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=("one" "two" "three four")
VALUE="two"
if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
echo "Couldn't find $VALUE"
fi
匹配是在完整的值上完成的,因此设置VALUE="three"将不匹配。