在Bash中,测试数组是否包含某个值的最简单方法是什么?
当前回答
这是一个小小的贡献:
array=(word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
注意:这种方法不区分大小写“两个单词”,但在问题中不需要这样做。
其他回答
我提出了这个方法,它只能在zsh中工作,但我认为一般方法是不错的。
arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
echo "found!"
else
echo "not found!"
fi
只有当${arr[@]/#pattern/}开始或${arr[@]/%pattern/}结束时,才能从每个元素中取出模式。这两个替换可以在bash中工作,但同时${arr[@]/#%pattern/}只能在zsh中工作。
如果修改后的数组等于原始数组,则不包含该元素。
编辑:
这个在bash中工作:
function contains () {
local arr=(${@:2})
local el=$1
local marr=(${arr[@]/#$el/})
[[ "${#arr[@]}" != "${#marr[@]}" ]]
}
在替换之后,它比较两个数组的长度。如果数组包含该元素,则替换将完全删除该元素,并且计数将不同。
如果你需要性能,你不希望每次搜索时都要遍历整个数组。
在这种情况下,您可以创建一个表示该数组索引的关联数组(哈希表或字典)。也就是说,它将每个数组元素映射到它在数组中的索引:
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"
我看到了一些解决这个问题的方法。
对于使用grep的索引数组
grep ${value} <<< ${array[*]} && true || false
对于使用grep的关联数组键
grep ${value} <<< "${!array[*]}" && true || false
我们可以使用awk,但它可能太过了。
awk --assign "v=${value}" '$v~$0 {print true}' <<<"${!array[*]}
Case语句。
case "${array[*]}" in (*${value}*) true ;; (*) false ;; esac
Bash条件表达式在ksh88风格双方括号:
[[ ${array[@]} =~ ${value} ]] && true || false
注意:顺序很重要,正则表达式在=~ match运算符的右边。
Bash for循环
for ((i=0;i<"${#array[*]}";i++)) ; [[ ${array[i]} = $value ]] && break 0 &> /dev/null || continue; done
注意,在这种特殊情况下,真逻辑是颠倒的,即1=真,0=假。这是因为我们使用break 0强制break内置除true之外的退出代码,除非break n参数小于1,否则总是如此。我们必须要打破循环,我们想要一个布尔退出码除了默认的'true',所以在这种情况下,我们翻转了逻辑。因此,使用具有返回true语义的函数可能更有意义。
别胡闹了!使您的解决方案简单、干净和可重用。
这些函数负责索引数组和关联数组。可以通过将搜索算法从线性搜索升级为二进制搜索(用于大型数据集)来改进它们。
##
# 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时,如果函数的名称可以让您知道迭代的对象(或不迭代的对象),则可能(可能)有用。
结合Beorn Harris和loentar的回答,我们得出了一个更有趣的单行测试:
delim=$'\x1F' # define a control code to be used as more or less reliable delimiter
if [[ "${delim}${array[@]}${delim}" =~ "${delim}a string to test${delim}" ]]; then
echo "contains 'a string to test'"
fi
它不使用额外的函数,不替换测试,并添加了额外的保护,防止使用控制代码作为分隔符偶尔出现错误匹配。
UPD:感谢@ChrisCogdon的注意,这个错误的代码被重写并以https://stackoverflow.com/a/58527681/972463的形式发布。