有没有办法让 sh/bash/zsh“case”使用变量作为测试值?

有没有办法让 sh/bash/zsh“case”使用变量作为测试值?

我想要如下内容:

#!/bin/sh
# ... other stuff ...
# some relatively static possibilities (srsp):
srsp='this|or|that|the|other'
# more other stuff
case $something in
    $srsp) # <- problem is here
        do_something # or maybe nothing
        ;;
    this|or|that|the|other);; # this would work, but loses the benefit of a variable
    *)
        # anything not in my list is an error:
        echo "Sorry, I don't recognize $something as one of $srsp" >&2
        exit 1;;
esac

do_something | egrep "blah($srsp)thing" # or whatever

问题是,$srsp那里只匹配整个字符串(如果$something恰好是字符串"this|or|that|or|some|other|stuff",它会匹配并调用do_something),而不是任何thisorthat......等等:我真正想要匹配的值。

如果我把这个文字字符串放在case语句中(我的“这会起作用”行),它会匹配我想要匹配的内容,但我试图保持这个干燥,并且我需要在稍后使用的正则表达式中使用相同的字符串集。(旁注:我知道匹配中可能出现的情况case和正则表达式中可能出现的情况可能会有很大差异,但在我的特定情况下,它们与两者都兼容。实际上,各个组件中只有字母,没有通配符,只有|,它在两个系统中都作为特殊情况存在。)

那么,有办法吗?特别是不将整个 case 语句包装在 eval 或其他东西中?我仍然想将其保留在 case 语句中,因为我还有其他事情要做。(我确信我可以通过重新构造并使用 egrep 作为我的匹配测试(if echo $something | egrep "^($srsp)$" > /dev/null或类似的东西)来实现一种解决方法。这个问题是关于试图找到一种不必诉诸于此的方法。或者明确知道它无法完成也是一个有效的答案。)

(或者我应该切换到 common-lisp? ;))

sh对于我的需求,我肯定会满意bash,并且可能 zsh,但如果有一种方法可以以最大程度可移植的方式(即sh)做到这一点,那么在我看来,这将是一个更好的答案。

答案1

这实际上与本身无关case:您需要一种机制来检查列表中是否存在某个单词。使用 bash 您可以这样做:

srsp=(this that or the other)
in_srsp() {
    # this joins the array into a string with spaces (default value of IFS)
    # then tests if the space-delimited word is a match for that string.
    [[ " ${srsp[*]} " == *" $1 "* ]]
}
something=foo
if in_srsp $something; then echo y; else echo n; fi   # =>  n
something=other
if in_srsp $something; then echo y; else echo n; fi   # =>  y

更便携,只需翻转外壳即可

srsp="this|that|or|the|other"
something=other
case "|$srsp|" in
    *"|$something|"*) echo in;;
    *) echo not;;
esac

答案2

eval当然,您可以使用,但它相当笨重。也许可以将其封装到函数中,这样您就可以避免在实际复杂的之外使用太多反斜杠eval

in_list () {
    local var
    var=$1
    local list
    list=$2
    eval case \$var in "$list"\) return 0\;\; \*) return 1\;\; esac
}

in_list "foo" "foo|bar|baz" && echo Success
in_list "nonesvch" "foo|bar|baz" || echo More so

如果你知道你可以限制列表中的字符集,也许可以在安全case之前运行过滤eval,例如

in_list () {
    local var
    var=$1
    local list
    list=$2

    case $var in *[!-a-z0-9_]*) return 1;; esac

    # Additionally, allow wildcards and whitespace in list
    case $list in *[!?*| a-z0-9_-]*)
        echo "$0: Invalid characters in $list" >&2; return 2;;
    esac

    eval case \$var in "$list"\) return 0\;\; \*) return 1\;\; esac
}

相关内容