在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")类型的数组,而是用于换行分隔的脚本输出。


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

其他回答

借鉴Dennis Williamson的答案,下面的解决方案结合了数组、shell-safe引号和正则表达式,以避免需要:遍历循环;使用管道或其他子过程;或者使用非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正则表达式来匹配数组内容的字符串化版本。有六个重要的步骤来确保正则表达式匹配不会被数组中的值的巧妙组合所欺骗:

Construct the comparison string by using Bash's built-in printf shell-quoting, %q. Shell-quoting will ensure that special characters become "shell-safe" by being escaped with backslash \. Choose a special character to serve as a value delimiter. The delimiter HAS to be one of the special characters that will become escaped when using %q; that's the only way to guarantee that values within the array can't be constructed in clever ways to fool the regular expression match. I choose comma , because that character is the safest when eval'd or misused in an otherwise unexpected way. Combine all array elements into a single string, using two instances of the special character to serve as delimiter. Using comma as an example, I used ,,%q as the argument to printf. This is important because two instances of the special character can only appear next to each other when they appear as the delimiter; all other instances of the special character will be escaped. Append two trailing instances of the delimiter to the string, to allow matches against the last element of the array. Thus, instead of comparing against ${array_str}, compare against ${array_str},,. If the target string you're searching for is supplied by a user variable, you must escape all instances of the special character with a backslash. Otherwise, the regular expression match becomes vulnerable to being fooled by cleverly-crafted array elements. Perform a Bash regular expression match against the string.

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

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
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不能通过引用传递变量。

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

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"

etc.

如果您想做一个快速而复杂的测试,看看是否值得遍历整个数组以获得精确匹配,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

这将输出“Checking”和“Match”。使用array=(word "two words" something),它只会输出"Checking"。使用array=(单词“two widgets”什么的)将没有输出。