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


当前回答

这对我来说很管用:

# 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

其他回答

containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

现在正确处理空数组。

一行的解决方案

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,你指出了数组项中可以存在换行符的情况。

我看到了一些解决这个问题的方法。

对于使用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语义的函数可能更有意义。

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

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

这种方法的优点是不需要遍历所有元素(至少不是显式地)。但是由于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"