在Bash中,测试数组是否包含某个值的最简单方法是什么?
当前回答
一行的解决方案
printf '%s\0' "${myarray[@]}" | grep -F -x -z -- 'myvalue'
解释
printf语句打印数组中的每个元素,以空字符分隔。
grep语句使用以下标志来匹配一个包含myvalue字符串的项(不多不少):
-z/——null-data -行以0字节而不是换行符结束。 -f /——fixed-strings -将pattern解释为固定字符串,而不是正则表达式。 -x/——line-regexp -只选择与整行完全匹配的匹配项。 ——-标记命令行选项的结束,使Grep处理“myvalue”作为一个非选项参数,即使它以破折号开始
为什么我们使用空字节\0而不是换行符\n?数组的元素实际上可能包含换行符。(如果您知道它没有,请随意删除-z grep选项,并将%s\n替换为您的第一个printf参数。)
使用
把这个放进一个if…然后声明:
if printf '%s\0' "${myarray[@]}" | grep -Fxqz -- 'myvalue'; then
# ...
fi
我在grep表达式中添加了-q标志,这样它就不会打印匹配项;它只会将匹配的存在视为“真”。
更新:感谢presto8指出——line-regexp标志。谢谢Tino,你指出了数组项中可以存在换行符的情况。
其他回答
保持简单:
Array1=( "item1" "item2" "item3" "item-4" )
var="item3"
count=$(echo ${Array1[@]} | tr ' ' '\n' | awk '$1 == "'"$var"'"{print $0}' | wc -l)
[ $count -eq 0 ] && echo "Not found" || echo "found"
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
现在正确处理空数组。
for i in "${array[@]}"
do
if [ "$i" -eq "$yourValue" ] ; then
echo "Found"
fi
done
字符串:
for i in "${array[@]}"
do
if [ "$i" == "$yourValue" ] ; then
echo "Found"
fi
done
扩展上面来自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
: NeedleInArgs "$needle" "${haystack[@]}"
: NeedleInArgs "$needle" arg1 arg2 .. argN
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${@:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
使用:
NeedleInArgs "$needle" "${haystack[@]}" && echo "$needle" found || echo "$needle" not found;
对于bash v3.1及以上版本(printf -v支持) 没有分叉,也没有外部程序 没有循环(除了bash中的内部扩展) 适用于所有可能的值和数组,没有异常,没有什么可担心的
也可以直接使用,比如:
if NeedleInArgs "$input" value1 value2 value3 value4;
then
: input from the list;
else
: input not from list;
fi;
对于从v20.5 b到v3.0的bash, printf缺少-v,因此需要额外的2个fork(但不需要执行,因为printf是bash内置的):
NeedleInArgs()
{
case $'\n'"`printf '%q\n' "${@:2}"`" in
(*"`printf '\n%q\n' "$1"`"*) return 0;;
esac;
return 1;
}
注意,我测试了时间:
check call0: n: t4.43 u4.41 s0.00 f: t3.65 u3.64 s0.00 l: t4.91 u4.90 s0.00 N: t5.28 u5.27 s0.00 F: t2.38 u2.38 s0.00 L: t5.20 u5.20 s0.00
check call1: n: t3.41 u3.40 s0.00 f: t2.86 u2.84 s0.01 l: t3.72 u3.69 s0.02 N: t4.01 u4.00 s0.00 F: t1.15 u1.15 s0.00 L: t4.05 u4.05 s0.00
check call2: n: t3.52 u3.50 s0.01 f: t3.74 u3.73 s0.00 l: t3.82 u3.80 s0.01 N: t2.67 u2.67 s0.00 F: t2.64 u2.64 s0.00 L: t2.68 u2.68 s0.00
Call0和call1是对另一个快速pure-bash变体调用的不同变体 Call2在这里。 N=notfound F=firstmatch L=lastmatch 小写字母为短数组,大写字母为长数组
正如您所看到的,这里的这个变体有一个非常稳定的运行时,所以它不太依赖于匹配位置。运行时主要由数组长度决定。搜索变量的运行时高度依赖于匹配位置。所以在边缘情况下,这个变体可以(快得多)。
但非常重要的是,搜索变量的RAM效率更高,因为这里的这个变量总是将整个数组转换为一个大字符串。
所以如果你的内存很紧,你希望大部分比赛都是早期的,那么就不要在这里使用这个。但是,如果您想要一个可预测的运行时,有很长的数组来匹配(期望延迟或根本不匹配),并且双RAM使用也不是太大的问题,那么这里有一些优势。
定时测试脚本:
in_array()
{
local needle="$1" arrref="$2[@]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${@:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
loop1() { for a in {1..100000}; do "$@"; done }
loop2() { for a in {1..1000}; do "$@"; done }
run()
{
needle="$5"
arr=("${@:6}")
out="$( ( time -p "loop$2" "$3" ) 2>&1 )"
ret="$?"
got="${out}"
syst="${got##*sys }"
got="${got%"sys $syst"}"
got="${got%$'\n'}"
user="${got##*user }"
got="${got%"user $user"}"
got="${got%$'\n'}"
real="${got##*real }"
got="${got%"real $real"}"
got="${got%$'\n'}"
printf ' %s: t%q u%q s%q' "$1" "$real" "$user" "$syst"
[ -z "$rest" ] && [ "$ret" = "$4" ] && return
printf 'FAIL! expected %q got %q\n' "$4" "$ret"
printf 'call: %q\n' "$3"
printf 'out: %q\n' "$out"
printf 'rest: %q\n' "$rest"
printf 'needle: %q\n' "$5"
printf 'arr: '; printf ' %q' "${@:6}"; printf '\n'
exit 1
}
check()
{
printf 'check %q: ' "$1"
run n 1 "$1" 1 needle a b c d
run f 1 "$1" 0 needle needle a b c d
run l 1 "$1" 0 needle a b c d needle
run N 2 "$1" 1 needle "${rnd[@]}"
run F 2 "$1" 0 needle needle "${rnd[@]}"
run L 2 "$1" 0 needle "${rnd[@]}" needle
printf '\n'
}
call0() { chk=("${arr[@]}"); in_array "$needle" chk; }
call1() { in_array "$needle" arr; }
call2() { NeedleInArgs "$needle" "${arr[@]}"; }
rnd=()
for a in {1..1000}; do rnd+=("$a"); done
check call0
check call1
check call2