在Bash中,测试数组是否包含某个值的最简单方法是什么?
当前回答
我看到了一些解决这个问题的方法。
对于使用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语义的函数可能更有意义。
其他回答
扩展上面来自Sean DiSanti的答案,我认为下面是一个简单而优雅的解决方案,它避免了对数组进行循环,并且不会由于部分匹配而给出假阳性
function is_in_array {
local ELEMENT="${1}"
local DELIM=","
printf "${DELIM}%s${DELIM}" "${@:2}" | grep -q "${DELIM}${ELEMENT}${DELIM}"
}
可以这样称呼:
$ haystack=("needle1" "needle2" "aneedle" "spaced needle")
$ is_in_array "needle" "${haystack[@]}"
$ echo $?
1
$ is_in_array "needle1" "${haystack[@]}"
$ echo $?
0
这种方法的优点是不需要遍历所有元素(至少不是显式地)。但是由于array.c中的array_to_string_internal()仍然循环遍历数组元素并将它们连接到一个字符串中,因此它可能并不比所提出的循环解决方案更有效,但它更具可读性。
if [[ " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array contains value
fi
if [[ ! " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array doesn't contain value
fi
请注意,如果您正在搜索的值是带有空格的数组元素中的某个单词,则会给出假阳性。例如
array=("Jack Brown")
value="Jack"
正则表达式将“Jack”视为在数组中,即使它不在数组中。所以你必须改变IFS和正则表达式上的分隔符如果你仍然想使用这个解决方案,就像这样
IFS="|"
array=("Jack Brown${IFS}Jack Smith")
value="Jack"
if [[ "${IFS}${array[*]}${IFS}" =~ "${IFS}${value}${IFS}" ]]; then
echo "true"
else
echo "false"
fi
unset IFS # or set back to original IFS if previously set
这将打印“false”。
显然,这也可以用作测试语句,允许将其表示为一行程序
[[ " ${array[*]} " =~ " ${value} " ]] && echo "true" || echo "false"
没有'grep'和循环的一行检查
if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
echo "array contains '$item'"
else
echo "array does not contain '$item'"
fi
这种方法既不使用grep这样的外部实用程序,也不使用循环。
这里发生的是:
we use a wildcard substring matcher to find our item in the array that is concatenated into a string; we cut off possible false positives by enclosing our search item between a pair of delimiters; we use a non-printable character as delimiter, to be on the safe side; we achieve our delimiter being used for array concatenation too by temporary replacement of the IFS variable value; we make this IFS value replacement temporary by evaluating our conditional expression in a sub-shell (inside a pair of parentheses)
使用grep和printf
在新行上格式化每个数组成员,然后grep这些行。
if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
example:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true
注意,这对delimeter和空格没有问题。
回答完之后,我读到了另一个我特别喜欢的答案,但它有缺陷,遭到了不好评。我受到了启发,这里有两种可行的新方法。
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>