检查Bash数组是否包含值

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

820366 次浏览
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

如果你想做一个快速而肮脏的测试,看看是否值得迭代整个数组以获得精确的匹配,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

这将输出“检查”和“匹配”。对于array=(word "two words" something),它只会输出“检查”。对于array=(word "two widgets" something),将没有输出。

$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found

这里有一个小小的贡献:

array=(word "two words" words)
search_string="two"
match=$(echo "${array[@]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"

注意:这种方式不能区分大小写“两个词”,但这在问题中不是必需的。

下面是一个实现此目的的小函数。搜索字符串是第一个参数,其余是数组元素:

set +e #otherwise the script will exit on error
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}

该函数的测试运行可能如下所示:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

现在正确处理空数组。

另一个没有函数的班轮:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

感谢@Qwerty关于空间的提醒!

对应功能:

find_in_array() {
local word=$1
shift
for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
return 1
}

例子:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"

以下代码检查给定值是否在数组中并返回其从零开始的偏移量:

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=“三”将不匹配。

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

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

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"

这种方法的优点是不需要循环所有元素(至少不明确)。但是由于数组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"

如果您不想迭代,这可能值得研究:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
echo "Value was found"
fi
exit

片段改编自:http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ 我认为这是非常聪明的。

编辑: 你可以这样做:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

但是后者只有在数组包含唯一值时才有效。在“143”中查找1会给出误报,我想。

给定:

array=("something to search for" "a string" "test2000")
elem="a string"

然后简单检查:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
echo "$elem exists in array"
fi

在哪里

c is element separator
p is regex pattern

(单独赋值p的原因,而不是直接在 [[ ]] 中使用表达式是为了保持bash 4的兼容性)

我通常编写这些实用程序来操作变量的名称,而不是变量值,主要是因为bash不能通过引用传递变量。

这是一个使用数组名称的版本:

function array_contains # array value
{
[[ -n "$1" && -n "$2" ]] || {
echo "usage: array_contains <array> <value>"
echo "Returns 0 if array contains value, 1 otherwise"
return 2
}


eval 'local values=("${'$1'[@]}")'


local element
for element in "${values[@]}"; do
[[ "$element" == "$2" ]] && return 0
done
return 1
}

这样,问题示例就变成了:

array_contains A "one" && echo "contains one"

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

有点晚了,但你可以用这个:

#!/bin/bash
# isPicture.sh


FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension


FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)


NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file


# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
echo "The extension '"$EXT"' is not a valid image extension."
exit
fi

我通常只使用:

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

非零值表示找到匹配项。

…实际上,为了解决上面提到的问题,如果你只想要一个精确的匹配,没有更多,没有更少,只需在-o之后添加一个w标志即可进行整个单词匹配:

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

我想出了这个,结果证明它只在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[@]}" ]]
}

在替换之后,它会比较两个数组的长度。如果数组包含元素,则替换将完全删除它,并且计数将不同。

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

虽然这里有几个很棒且有用的答案,但我没有找到一个似乎是高性能、跨平台和健壮的正确组合;所以我想分享我为我的代码编写的解决方案:

#!/bin/bash


# array_contains "$needle" "${haystack[@]}"
#
# Returns 0 if an item ($1) is contained in an array ($@).
#
# Developer note:
#    The use of a delimiter here leaves something to be desired. The ideal
#    method seems to be to use `grep` with --line-regexp and --null-data, but
#    Mac/BSD grep doesn't support --line-regexp.
function array_contains()
{
# Extract and remove the needle from $@.
local needle="$1"
shift


# Separates strings in the array for matching. Must be extremely-unlikely
# to appear in the input array or the needle.
local delimiter='#!-\8/-!#'


# Create a string with containing every (delimited) element in the array,
# and search it for the needle with grep in fixed-string mode.
if printf "${delimiter}%s${delimiter}" "$@" | \
grep --fixed-strings --quiet "${delimiter}${needle}${delimiter}"; then
return 0
fi


return 1
}

扩展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

我已经建议的正则表达式技术版本:

values=(foo bar)
requestedValue=bar


requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

这里发生的事情是,你正在将整个支持的值数组扩展为单词,并在每个单词之前添加一个特定的字符串,在这种情况下是“X-”,并对请求的值执行相同的操作。如果这个确实包含在数组中,那么结果字符串最多匹配一个结果标记,或者根本不匹配。在后一种情况下,||运算符会触发,你知道你正在处理一个不支持的值。在此之前,通过标准的shell字符串操作,请求的值会被剥离所有前导和尾随空格。

我相信它干净而优雅,尽管我不太确定如果您支持的值数组特别大,它的性能会有多好。

以下是我对这个问题的看法。这是简短的版本:

function arrayContains() {
local haystack=${!1}
local needle="$2"
printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

还有长版,我认为这对眼睛来说容易得多。

# With added utility function.
function arrayToLines() {
local array=${!1}
printf "%s\n" ${array[@]}
}


function arrayContains() {
local haystack=${!1}
local needle="$2"
arrayToLines haystack[@] | grep -q "^$needle$"
}

示例:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False

使用grepprintf

在新行上格式化每个数组成员,然后grep行。

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
例子:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

请注意,这对于分隔符和空格没有问题。

回答完后,我读了另一个我特别喜欢的答案,但它有缺陷,被否决了。我受到了启发,这里有两个我认为可行的新方法。

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

使用grepprintf

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

使用for

(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>

这是为我工作:

# 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循环,因为这需要时间运行。如果必须循环,让它用比外壳脚本更低级别的语言编写。

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…好吧,你可以自由使用另一种方法。:-)

结合这里提出的一些想法,您可以制作一个没有循环的优雅if语句完全匹配

find="myword"
array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
echo "Array contains myword";
fi

这不会在wordval上触发,只有整个单词匹配。如果每个数组值包含多个单词,它将中断。

借鉴丹尼斯·威廉姆森回答,以下解决方案结合了数组、shell安全引用和正则表达式,以避免需要:遍历循环;使用管道或其他子进程;或使用非bash实用程序。

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"


if [[ "${array_str},," =~ ,,words,, ]]
then
echo 'Matches'
else
echo "Doesn't match"
fi

上面的代码通过使用Bash正则表达式来匹配数组内容的字符串版本来工作。有六个重要步骤来确保正则表达式匹配不会被数组中值的巧妙组合所欺骗:

  1. 使用Bash的内置printf shell引用%q构造比较字符串。shell引用将确保特殊字符通过使用反斜杠\进行转义而变得“shell安全”。
  2. 选择一个特殊字符作为值分隔符。分隔符必须是使用%q时转义的特殊字符之一;这是保证数组中的值不能以巧妙的方式构造以欺骗正则表达式匹配的唯一方法。我选择逗号,是因为该字符在以意想不到的方式进行评估或滥用时是最安全的。
  3. 将所有数组元素组合成一个字符串,使用特殊字符的两个实例作为分隔符。以逗号为例,我使用,,%q作为printf的参数。这很重要,因为特殊字符的两个实例在作为分隔符出现时只能相邻显示;特殊字符的所有其他实例都将被转义。
  4. 将分隔符的两个尾随实例附加到字符串中,以允许与数组的最后一个元素匹配。因此,不是与${array_str}进行比较,而是与${array_str},,进行比较。
  5. 如果您要搜索的目标字符串是由用户变量提供的,您必须将使用反斜杠转义特殊字符的所有实例。否则,正则表达式匹配很容易被精心设计的数组元素欺骗。
  6. 对字符串执行Bash正则表达式匹配。

单线解决方案

printf '%s\0' "${myarray[@]}" | grep -F -x -z -- 'myvalue'

补充说明

printf语句打印数组的每个元素,由空字符分隔。

grep语句使用以下标志来匹配包含完全的项目,字符串为myvalue(不多不少):

  • -z/--null-data-行以零字节而不是换行符终止。
  • -F/--fixed-strings-将模式解释为固定字符串,而不是正则表达式。
  • -x/--line-regexp-仅选择与整行完全匹配的匹配项。
  • ---标志着命令行选项的结束,使Grep进程“myvalue”作为非选项参数,即使它以破折号开头

为什么我们使用空字节\0而不是换行符\n?您的数组实际上可能在其元素中包含换行符。(如果您知道它不包含,请随意删除-z grep选项并替换%s\n作为您的第一个printf参数。)


用法

要将其放入if ... then语句中:

if printf '%s\0' "${myarray[@]}" | grep -Fxqz -- 'myvalue'; then
# ...
fi

我在grep表达式中添加了一个-q标志,这样它就不会打印匹配;它只会将匹配的存在视为“true”。

更新:感谢presto8指出--line-regexp标志。感谢Tino指出换行符可以存在于数组项中的情况。

我有这样的情况,我必须检查一个ID是否包含在另一个脚本/命令生成的ID列表中。 我做了以下工作:

# the ID I was looking for
ID=1


# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "


# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)


# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
echo "not found"
fi
# etc.

你也可以像这样缩短/压缩它:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
echo "not found"
fi

在我的情况下,我正在运行jq来过滤一些JSON以获取ID列表,然后必须检查我的ID是否在这个列表中,这对我来说是最好的。 它不适用于手动创建的LIST=("1" "2" "4")类型的数组,但适用于换行符分隔的脚本输出。


PS:无法评论答案,因为我相对较新…

@ghostdog74关于使用case逻辑检查数组是否包含特定值的回答的一个小补充:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

或者打开extglob选项,您可以这样做:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

我们也可以用if语句来做到这一点:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi

没有“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等外部实用程序,也不使用循环。

这里发生的事情是:

  • 我们使用通配符子字符串匹配器在连接成字符串的数组中查找我们的项目;
  • 我们通过将搜索项包含在一对分隔符之间来切断可能的误报;
  • 为了安全起见,我们使用不可打印的字符作为分隔符;
  • 我们通过临时替换IFS变量值来实现我们的分隔符也用于数组连接;
  • 我们通过计算子shell中的条件表达式(在一对括号内)来临时替换这个IFS

使用参数扩展:

${参数:+word}如果参数为空或未设置,则没有任何内容 替换,否则替换词的扩展。

declare -A myarray
myarray[hello]="world"


for i in hello goodbye 123
do
if [ ${myarray[$i]:+_} ]
then
echo ${!myarray[$i]} ${myarray[$i]}
else
printf "there is no %s\n" $i
fi
done

以下是几种可能的实现的汇编,包括集成验证和简单的基准测试(需要Bash>=4.0):

#!/usr/bin/env bash


# Check if array contains item [$1: item, $2: array name]
function in_array_1() {
local needle="$1" item
local -n arrref="$2"
for item in "${arrref[@]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2: array name]
function in_array_2() {
local needle="$1" arrref="$2[@]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2: array name]
function in_array_3() {
local needle="$1" i
local -n arrref="$2"
for ((i=0; i < ${#arrref[@]}; i++)); do
[[ "${arrref[i]}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2..$n: array items]
function in_array_4() {
local needle="$1" item
shift
for item; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2..$n: array items]
function in_array_5() {
local needle="$1" item
for item in "${@:2}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2: array name]
function in_array_6() {
local needle="$1" arrref="$2[@]" array i
array=("${!arrref}")
for ((i=0; i < ${#array[@]}; i++)); do
[[ "${array[i]}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2..$n: array items]
function in_array_7() {
local needle="$1" array=("${@:2}") item
for item in "${array[@]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}


# Check if array contains item [$1: item, $2..$n: array items]
function in_array_8() {
local needle="$1"
shift
while (( $# > 0 )); do
[[ "$1" == "${needle}" ]] && return 0
shift
done
return 1
}




#------------------------------------------------------------------------------




# Generate map for array [$1: name of source array, $2: name of target array]
# NOTE: target array must be pre-declared by caller using 'declare -A <name>'
function generate_array_map() {
local -n srcarr="$1" dstmap="$2"
local i key
dstmap=()
for i in "${!srcarr[@]}"; do
key="${srcarr[i]}"
[[ -z ${dstmap["${key}"]+set} ]] && dstmap["${key}"]=${i} || dstmap["${key}"]+=,${i}
done
}


# Check if array contains item [$1: item, $2: name of array map]
function in_array_9() {
local needle="$1"
local -n mapref="$2"
[[ -n "${mapref["${needle}"]+set}" ]] && return 0 || return 1
}




#------------------------------------------------------------------------------




# Test in_array function [$1: function name, $2: function description, $3: test array size]
function test() {
local tname="$1" tdesc="$2" tn=$3 ti=0 tj=0 ta=() tct=0 tepapre="" tepapost="" tepadiff=()
local -A tam=()


echo -e "\e[1m${tname} (${tdesc}):\e[0m"


# Generate list of currently defined variables
tepapre="$(compgen -v)"


# Fill array with random items
for ((ti=0; ti < ${tn}; ti++)); do
ta+=("${RANDOM} ${RANDOM} ${RANDOM} ${RANDOM}")
done


# Determine function call type (pass array items, pass array name, pass array map)
case "${tname}" in
"in_array_1"|"in_array_2"|"in_array_3"|"in_array_6") tct=0; ;;
"in_array_4"|"in_array_5"|"in_array_7"|"in_array_8") tct=1; ;;
"in_array_9") generate_array_map ta tam; tct=2; ;;
*) echo "Unknown in_array function '${tname}', aborting"; return 1; ;;
esac


# Verify in_array function is working as expected by picking a few random
# items and checking
echo -e "\e[1mVerification...\e[0m"
for ((ti=0; ti < 10; ti++)); do
tj=$(( ${RANDOM} % ${#ta[@]} ))
echo -n "Item ${tj} '${ta[tj]}': "
if (( ${tct} == 0 )); then
"${tname}" "${ta[tj]}" ta && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" ta && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 1 )); then
"${tname}" "${ta[tj]}" "${ta[@]}" && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" "${ta[@]}" && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[tj]}" tam && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" tam && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
fi
echo
done


# Benchmark in_array function
echo -en "\e[1mBenchmark...\e[0m"
time for ((ti=0; ti < ${#ta[@]}; ti++)); do
if (( ${tct} == 0 )); then
"${tname}" "${ta[ti]}" ta
elif (( ${tct} == 1 )); then
"${tname}" "${ta[ti]}" "${ta[@]}"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[ti]}" tam
fi
done


# Generate list of currently defined variables, compare to previously
# generated list to determine possible environment pollution
echo -e "\e[1mEPA test...\e[0m"
tepapost="$(compgen -v)"
readarray -t tepadiff < <(echo -e "${tepapre}\n${tepapost}" | sort | uniq -u)
if (( ${#tepadiff[@]} == 0 )); then
echo -e "\e[1;32mclean\e[0m"
else
echo -e "\e[1;31mpolluted:\e[0m ${tepadiff[@]}"
fi


echo
}




#------------------------------------------------------------------------------




# Test in_array functions
n=5000
echo
( test in_array_1 "pass array name, nameref reference, for-each-loop over array items" ${n} )
( test in_array_2 "pass array name, indirect reference, for-each-loop over array items" ${n} )
( test in_array_3 "pass array name, nameref reference, c-style for-loop over array items by index" ${n} )
( test in_array_4 "pass array items, for-each-loop over arguments" ${n} )
( test in_array_5 "pass array items, for-each-loop over arguments as array" ${n} )
( test in_array_6 "pass array name, indirect reference + array copy, c-style for-loop over array items by index" ${n} )
( test in_array_7 "pass array items, copy array from arguments as array, for-each-loop over array items" ${n} )
( test in_array_8 "pass array items, while-loop, shift over arguments" ${n} )
( test in_array_9 "pre-generated array map, pass array map name, direct test without loop" ${n} )

结果:

in_array_1 (pass array name, nameref reference, for-each-loop over array items):
Verification...
Item 862 '19528 10140 12669 17820': ok ok
Item 2250 '27262 30442 9295 24867': ok ok
Item 4794 '3857 17404 31925 27993': ok ok
Item 2532 '14553 12282 26511 32657': ok ok
Item 1911 '21715 8066 15277 27126': ok ok
Item 4289 '3081 10265 16686 19121': ok ok
Item 4837 '32220 1758 304 7871': ok ok
Item 901 '20652 23880 20634 14286': ok ok
Item 2488 '14578 8625 30251 9343': ok ok
Item 4165 '4514 25064 29301 7400': ok ok
Benchmark...
real    1m11,796s
user    1m11,262s
sys     0m0,473s
EPA test...
clean


in_array_2 (pass array name, indirect reference, for-each-loop over array items):
Verification...
Item 2933 '17482 25789 27710 2096': ok ok
Item 3584 '876 14586 20885 8567': ok ok
Item 872 '176 19749 27265 18038': ok ok
Item 595 '6597 31710 13266 8813': ok ok
Item 748 '569 9200 28914 11297': ok ok
Item 3791 '26477 13218 30172 31532': ok ok
Item 2900 '3059 8457 4879 16634': ok ok
Item 676 '23511 686 589 7265': ok ok
Item 2248 '31351 7961 17946 24782': ok ok
Item 511 '8484 23162 11050 426': ok ok
Benchmark...
real    1m11,524s
user    1m11,086s
sys     0m0,437s
EPA test...
clean


in_array_3 (pass array name, nameref reference, c-style for-loop over array items by index):
Verification...
Item 1589 '747 10250 20133 29230': ok ok
Item 488 '12827 18892 31996 1977': ok ok
Item 801 '19439 25243 24485 24435': ok ok
Item 2588 '17193 18893 21610 9302': ok ok
Item 4436 '7100 655 8847 3068': ok ok
Item 2620 '19444 6457 28835 24717': ok ok
Item 4398 '4420 16336 612 4255': ok ok
Item 2430 '32397 2402 12631 29774': ok ok
Item 3419 '906 5361 32752 7698': ok ok
Item 356 '9776 16485 20838 13330': ok ok
Benchmark...
real    1m17,037s
user    1m17,019s
sys     0m0,005s
EPA test...
clean


in_array_4 (pass array items, for-each-loop over arguments):
Verification...
Item 1388 '7932 15114 4025 15625': ok ok
Item 3900 '23863 25328 5632 2752': ok ok
Item 2678 '31296 4216 17485 8874': ok ok
Item 1893 '16952 29047 29104 23384': ok ok
Item 1616 '19543 5999 4485 22929': ok ok
Item 93 '14456 2806 12829 19552': ok ok
Item 265 '30961 19733 11863 3101': ok ok
Item 4615 '10431 9566 25767 13518': ok ok
Item 576 '11726 15104 11116 74': ok ok
Item 3829 '19371 25026 6252 29478': ok ok
Benchmark...
real    1m30,912s
user    1m30,740s
sys     0m0,011s
EPA test...
clean


in_array_5 (pass array items, for-each-loop over arguments as array):
Verification...
Item 1012 '29213 31971 21483 30225': ok ok
Item 2802 '4079 5423 29240 29619': ok ok
Item 473 '6968 798 23936 6852': ok ok
Item 2183 '20734 4521 30800 2126': ok ok
Item 3059 '14952 9918 15695 19309': ok ok
Item 1424 '25784 28380 14555 21893': ok ok
Item 1087 '16345 19823 26210 20083': ok ok
Item 257 '28890 5198 7251 3866': ok ok
Item 3986 '29035 19288 12107 3857': ok ok
Item 2509 '9219 32484 12842 27472': ok ok
Benchmark...
real    1m53,485s
user    1m53,404s
sys     0m0,077s
EPA test...
clean


in_array_6 (pass array name, indirect reference + array copy, c-style for-loop over array items by index):
Verification...
Item 4691 '25498 10521 20673 14948': ok ok
Item 263 '25265 29824 3876 14088': ok ok
Item 2550 '2416 14274 12594 29740': ok ok
Item 2269 '2769 11436 3622 28273': ok ok
Item 3246 '23730 25956 3514 17626': ok ok
Item 1059 '10776 12514 27222 15640': ok ok
Item 53 '23813 13365 16022 4092': ok ok
Item 1503 '6593 23540 10256 17818': ok ok
Item 2452 '12600 27404 30960 26759': ok ok
Item 2526 '21190 32512 23651 7865': ok ok
Benchmark...
real    1m54,793s
user    1m54,326s
sys     0m0,457s
EPA test...
clean


in_array_7 (pass array items, copy array from arguments as array, for-each-loop over array items):
Verification...
Item 2212 '12127 12828 27570 7051': ok ok
Item 1393 '19552 26263 1067 23332': ok ok
Item 506 '18818 8253 14924 30710': ok ok
Item 789 '9803 1886 17584 32686': ok ok
Item 1795 '19788 27842 28044 3436': ok ok
Item 376 '4372 16953 17280 4031': ok ok
Item 4846 '19130 6261 21959 6869': ok ok
Item 2064 '2357 32221 22682 5814': ok ok
Item 4866 '10928 10632 19175 14984': ok ok
Item 1294 '8499 11885 5900 6765': ok ok
Benchmark...
real    2m35,012s
user    2m33,578s
sys     0m1,433s
EPA test...
clean


in_array_8 (pass array items, while-loop, shift over arguments):
Verification...
Item 134 '1418 24798 20169 9501': ok ok
Item 3986 '12160 12021 29794 29236': ok ok
Item 1607 '26633 14260 18227 898': ok ok
Item 2688 '18387 6285 2385 18432': ok ok
Item 603 '1421 306 6102 28735': ok ok
Item 625 '4530 19718 30900 1938': ok ok
Item 4033 '9968 24093 25080 8179': ok ok
Item 310 '6867 9884 31231 29173': ok ok
Item 661 '3794 4745 26066 22691': ok ok
Item 4129 '3039 31766 6714 4921': ok ok
Benchmark...
real    5m51,097s
user    5m50,566s
sys     0m0,495s
EPA test...
clean


in_array_9 (pre-generated array map, pass array map name, direct test without loop):
Verification...
Item 3696 '661 6048 13881 26901': ok ok
Item 815 '29729 13733 3935 20697': ok ok
Item 1076 '9220 3405 18448 7240': ok ok
Item 595 '8912 2886 13678 24066': ok ok
Item 2803 '13534 23891 5344 652': ok ok
Item 1810 '12528 32150 7050 1254': ok ok
Item 4055 '21840 7436 1350 15443': ok ok
Item 2416 '19550 28434 17110 31203': ok ok
Item 1630 '21054 2819 7527 953': ok ok
Item 1044 '30152 22211 22226 6950': ok ok
Benchmark...
real    0m0,128s
user    0m0,128s
sys     0m0,000s
EPA test...
clean

得票最多的答案非常简洁和干净,但是当空格是数组元素之一的一部分时,它可能会产生误报。当更改IFS并使用"${array[*]}"而不是"${array[@]}"时,可以克服这一点。方法是相同的,但看起来不那么干净。通过使用"${array[*]}",我们打印$array的所有元素,由IFS中的第一个字符分隔。因此,通过选择正确的IFS,您可以克服这个特殊问题。在这个特殊情况下,我们决定将IFS设置为一个不常见的字符$'\001',代表"${array[@]}"0("${array[@]}"1)

$ array=("foo bar" "baz" "qux")
$ IFS=$'\001'
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo${IFS}" ]] && echo yes || echo no
no
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo bar${IFS}" ]] && echo yes || echo no
yes
$ unset IFS

这解决了大多数误报问题,但需要选择IFS

注:如果之前设置了IFS,最好保存并重置它,而不是使用unset IFS


相关:

OP本身添加了以下答案,并附有评论:

在答案和评论的帮助下,经过一些测试,我想出了这个:

function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}


A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
echo "contains three"
fi

如何检查Bash数组是否包含值


假阳性匹配

array=(a1 b1 c1 d1 ee)


[[ ${array[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
# output:
yes


[[ ${array[*]} =~ 'a1' ]] && echo 'yes' || echo 'no'
# output:
yes


[[ ${array[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
# output:
yes


[[ ${array[*]} =~ 'ee' ]] && echo 'yes' || echo 'no'
# output:
yes

完全吻合

为了寻找完全匹配,您的正则表达式模式需要在值之前和之后添加额外的空间,例如(^|[[:space:]])"VALUE"($|[[:space:]])

# Exact match


array=(aa1 bc1 ac1 ed1 aee)


if [[ ${array[*]} =~ (^|[[:space:]])"a"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
No


if [[ ${array[*]} =~ (^|[[:space:]])"ac1"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes


find="ac1"
if [[ ${array[*]} =~ (^|[[:space:]])"$find"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes

有关更多使用示例的示例来源是这里

保持简单:

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"
: 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;

对于从v2.05b到v3.0的bashprintf缺少-v,因此需要2个额外的分叉(但没有高管,因为printfbash内置):

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
  • call0call1是调用另一个快速纯bash变体的不同变体
  • call2在这里。
  • N=没找到F=第一次匹配L=最后一次匹配
  • 小写字母是短数组,大写字母是长数组

正如你所看到的,这里的这个变体有一个非常稳定的运行时,所以它不太依赖于匹配位置。运行时主要由数组长度主导。搜索变体的运行时高度依赖于匹配位置。所以在边缘情况下,这里的这个变体可以(快得多)。

但非常重要的是,搜索变体的RAM效率更高,因为这里的这个变体总是将整个数组转换为一个大字符串。

因此,如果您的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

停止疯狂!让你的解决方案简单,干净,可重复使用。

这些函数包括索引数组和关联数组。可以通过将搜索算法从线性搜索升级到二进制搜索(用于大型数据集)来改进它们。

##
# 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时,如果可能(可能)有一个名称的函数,让您知道正在迭代(或不迭代)什么是有用的。

我看到了解决这个问题的几种方法。

对于使用grep的索引数组

grep ${value} <<< ${array[*]} && true || false

对于使用grep的关联数组键

grep ${value} <<< "${!array[*]}" && true || false

人们可以使用awk,但这可能是矫枉过正。

awk --assign "v=${value}" '$v~$0 {print true}' <<<"${!array[*]}

案例陈述。

case "${array[*]}" in (*${value}*) true ;; (*) false ;; esac

ksh88风格双方括号中的Bash条件表达式:

[[ ${array[@]} =~ ${value} ]] && true || false

注意:顺序很重要,正则表达式位于=~匹配运算符的右侧。

bashforloop

for ((i=0;i<"${#array[*]}";i++)) ; [[ ${array[i]} = $value ]] && break 0 &> /dev/null || continue; done

请注意,在这种特殊情况下,truthy逻辑是反转的,即1=true,0=false。这是因为我们使用break 0强制break内置除true之外的退出代码,除非break n参数小于1,否则总是如此。我们迫切希望打破循环,并且我们想要除了默认“true”之外的布尔退出代码,所以在这种情况下,我们翻转逻辑。出于这个原因,使用带有return true语义学的函数可能更有意义。