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


当前回答

我有这样的情况,我必须检查一个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")类型的数组,而是用于换行分隔的脚本输出。


附言:不能评论一个答案,因为我是相对较新的…

其他回答

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

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

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="three"将不匹配。

没有'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)

这是一个小小的贡献:

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

注意:这种方法不区分大小写“两个单词”,但在问题中不需要这样做。

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

values=(foo bar)
requestedValue=bar

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

What's happening here is that you're expanding the entire array of supported values into words and prepending a specific string, "X-" in this case, to each of them, and doing the same to the requested value. If this one is indeed contained in the array, then the resulting string will at most match one of the resulting tokens, or none at all in the contrary. In the latter case the || operator triggers and you know you're dealing with an unsupported value. Prior to all of that the requested value is stripped of all leading and trailing whitespace through standard shell string manipulation.

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