Bash 检查列表中的项目是否未按预期运行

Bash 检查列表中的项目是否未按预期运行

我正在尝试编写一个脚本,该脚本的条件基于变量出现在列表中:

#!/bin/bash

LIST=`ls`

function listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && return 0 || return 1
}

if [ $(listcontains "${LIST}" "multi.sh") ] ; then
     echo "Found!"
else
    echo "Failed :("
fi

$(listcontains "${LIST}" "multi.sh")
echo returned $?

列表中有一个名为“multi.sh”的文件,所以我期待“找到!”但上面的脚本报告“失败:(”。后续调用返回 0。

我试过

if [ 0 -eq $(listcontains "${LIST}" "multi.sh") ] ; then

但后来我得到一个错误

./script.sh: 第 9 行: [: 0: 需要一元运算符

失败的 :(

我在这里缺少什么?

答案1

if [ $(listcontains "${LIST}" "multi.sh") ] ; then

您没有从函数中打印任何内容,因此命令替换在经过分词后不会产生任何字段。这与 running 相同,并且和if [ ]; then ...之间没有任何参数,会返回虚假状态。[][

使用引号 , 执行此操作if [ "$(listcontains...)" ]; then不会有帮助,因为它只是将一个空字符串传递给[, 就像[ "" ],并且还会返回一个虚假状态。 (括号之间有一个参数,它检查该参数是否非空。)

仅当没有其他命令可运行时,命令替换本身的退出状态才可见,当您$(listcontains ...)稍后在一行中单独运行该命令时,就会出现这种情况。

如果您想通过命令替换来测试它,则需要打印一些内容。例如

listcontains() {
    if ...; then
        echo yes
    fi # else print nothing
}
if [ "$(listcontains ...)" ]; then
    echo ok
fi

(或与if [ "$(listcontains ...)" = yes ]; then ...

但这不是必需的,因为您可以直接查看退出状态:

listcontains() {
    if [[ ... ]]; then
        return 0
    fi
    return 1
}
if listcontains ...; then
    echo ok
fi

由于函数的退出状态是最后一个命令的退出状态,因此我们可以将函数简化为:

listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]]
}

但是您可能想要引用$2,以便内容将按字面意思处理,即使其中存在正则表达式特殊字符。

例如,按照上面的方式,listcontains 'foo matchxsh bar' match.sh会找到匹配项,因为它.匹配任何单个字符。像不匹配的括号之类的东西可能会出错。

您还可以在匹配之前在字符串的开头和结尾放置空格来缩短它,这样您就不需要关心是否命中 BOL/EOL:

listcontains() {
    [[ " $1 " =~ [[:space:]]"$2"[[:space:]] ]]
}

或者更多 POSIXly,这样它也可以在 Dash 中使用:

listcontains() {
    case " $1 " in
        *[[:space:]]"$2"[[:space:]]*) return 0;;
        *) return 1;;
    esac
}

答案2

检查某个项目是否在列表中的最简单方法之一是将该列表转换为关联数组,又名“哈希”(项目是键,以及任何任意值),然后测试您想要的项目是否是一个数组的索引。

我通常使用“0”或“1”作为每个键的值。有时我只是测试空字符串与非空字符串。这主要取决于我使用的语言以及它认为什么是正确的或错误的。

实际上,这是使用关联数组作为简单的并测试集合成员资格(如果键有值,则它是成员,如果没有,则不是),因此只要您知道要测试什么以及如何测试它,该值并不重要。

不需要正则表达式匹配,只需一个简单的测试:我正在寻找的项目是关联数组中的键吗?

测试集合成员资格也很快……对于一次性测试来说,性能并不重要,但如果您正在测试大量潜在的集合成员,那么性能就很重要。对于像 shell 这样极其缓慢的语言来说尤其如此。

下面是使用索引数组中包含的列表的示例。

$ items=(item1 item2 item3 item4)

$ declare -A itemhash
$ for i in "${items[@]}" ; do itemhash[$i]=1 ; done

这是索引数组和关联数组当前包含的内容:

$ declare -p items itemhash
declare -a items=([0]="item1" [1]="item2" [2]="item3" [3]="item4")
declare -A itemhash=([item1]="1" [item2]="1" [item3]="1" [item4]="1" )

好的,散列(关联数组)已填充,现在我们可以测试它是否包含特定项目:

$ if [ "${itemhash[item1]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

$ if [ "${itemhash[item5]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

此方法几乎适用于任何列表,无论项目的来源(索引数组、文件名列表、数据库查询的输出等),并且如何填充散列并不重要 -重要的是,散列的键应该是项目的名称,并且每个键的值应该是您可以轻松测试的值。


您不应该使用涉及文件名的示例,ls因为这会造成巨大的干扰......但这里是另一个示例,使用测试当前目录中的文件名列表中的文件名“multi.sh”。

首先,当multi.sh当前目录不存在时:

$ declare -A foo
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

然后创建multi.sh并重试:

$ unset foo ; declare -A foo
$ touch multi.sh
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

注意:我使用过printf '%s\0' *而不是ls因为解析 的输出ls是一个坏主意。读取以 NUL 分隔的文件名列表适用于任何有效的文件名,甚至包含换行符等烦人字符的文件名。

顺便说一句,最近版本的 GNUls有一个--zeroNUL 分隔输出的选项 - 我仍然不倾向于使用它,我宁愿使用printf ... *上面的或find.

相关内容