如何在Bash脚本中将AND和OR条件组合用于if条件?

11

我正在尝试在if条件中的bash脚本中结合逻辑AND和OR。不知何故,我没有得到预期的输出,而且很难进行故障排除。 我正在尝试验证传递给shell脚本的输入参数是否为空参数,并且第一个传递的参数是否有效。

if [ "$#" -ne 1 ] && ([ "$1" == "ABC" ] || [ "$1" == "DEF" ] || [ "$1" == "GHI" ] || [ "$1" == "JKL" ]) 
then
echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
exit 1
fi

有人能指出这里出了什么问题吗?

3个回答

15

你的陈述立即面临的问题是一个逻辑问题:你可能想要写成:

if [ "$#" -ne 1 ] || ! ([ "$1" = "ABC" ] || [ "$1" = "DEF" ] || [ "$1" = "GHI" ] || [ "$1" = "JKL" ]) 
then
  echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
  exit 1
fi

换言之,如果给出的参数大于1个或单个参数不等于可接受的值之一,则中止执行。

请注意,在圆括号中使用感叹号!来否定表达式,并使用符合 POSIX 标准的字符串相等运算符=(而非==)。

但是,考虑到您正在使用 Bash,您可以仅使用一个[[ ... ]]条件语句和Bash的正则表达式匹配运算符=~ 来实现:

if [[ $# -ne 1 || ! $1 =~ ^(ABC|DEF|GHI|JKL)$ ]] 
then
  echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
  exit 1
fi
如果不需要POSIX兼容性,[[ ... ]][ ... ]更可取,原因有很多(见链接)。在这种情况下,$#$1不需要引号,并且可以在条件语句内部使用||
请注意,上面使用的=~适用于Bash 3.2+,而在anubhava的有用答案中使用的隐式extglob语法要求Bash 4.1+;但是,在早期版本中您可以显式启用(并在之后恢复为其原始值)extglob shell选项:shopt -s extglob

8
BASH实际上允许在[[ ... ]]中使用扩展的glob,并且内部也可以使用&&。因此,您可以这样做:
if [[ $# -ne 1 && $1 == @(ABC|DEF|GHI|JKL) ]]; then
   echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
   exit 1
fi

1
是的,那个也可以,但这个更短,因为它只有一个 [[ ... ]] 表达式。 - anubhava
然而,我认为在这段代码中应该是$1 != ...,但我只是复制了 OP 的条件。 - anubhava
1
++,但请注意,隐式的“extglob”语法仅适用于Bash 4.1及以上版本。 - mklement0
2
在4.1版本之前,您只需要使用shopt -s extglob显式启用它。 - chepner

2
一些注意事项:
  • [...]在bash中等同于相同的test命令(请查看手册),因此这些&&和||不是逻辑运算符,而是shell的等效操作符。
  • 在POSIX shell中,括号不是分组运算符。它们可以在这里工作,但它们会打开一个子shell,您最好使用标准测试选项-a-o(使您的if语句为if [ "$#" -ne 1 -a \( "$1" == "ABC" -o "$1" == "DEF" -o "$1" == "GHI" -o "$1" == "JKL" \) ],尽管根据您的逻辑,听起来您实际上想要类似于if [ "$#" -ne 1 -o \( "$1" != "ABC" -a "$1" != "DEF" -a "$1" != "GHI" -a "$1" != "JKL" \) ]的东西。您可能可以通过以下case语句获得更好的结果:

usage() {
    echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
}


if [ "$#" -ne 1 ] 
then
    usage
    exit 1
fi

case "$1" in
    ABC)
        echo "Found ABC"
        ;;
    DEF)
        echo "Found DEF"
        ;;
    GHI)
        echo "Found GHI"
        ;;
    JKL)
        echo "Found JKL"
        ;;
    *)
        usage
        exit 1
        ;;
esac

如果你想传递一组可能的静态参数,你可以考虑使用getopts特殊的shell命令。

优点是:POSIX规范中的test本身警告不要使用-a-o():“指定-a和-o二进制主体以及'('和')'运算符的XSI扩展已被标记为过时。(许多使用它们的表达式在语法上根据正在评估的特定表达式而有歧义定义。)” - mklement0
这是一个很好的争论点。您如何仅使用POSIX完成此操作?命令组({...})、&&和||? - Taywee
1
@Taywee:没错,你可以使用 [ ... ] && { [ ... ] || [ ... ]; } - chepner
1
你也可以使用 ABC|DEF|GHI|JKL) echo "Found $1" ;; 来缩短 case 语句。 - chepner
命令组是一个好主意(避免子shell);值得指出的陷阱是:在test中,-a-o具有更高的优先级,但是shell的&&||具有相等的优先级。 - mklement0
1
@chepner 我知道,我假设它们可能会执行不同的操作,并且可能希望独立处理。 - Taywee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接