如何在Bash中将字符串与多个正确值进行比较?

115

我有下面这段 Bash 脚本:

function get_cms {
    echo "input cms name"
    read cms
    cms=${cms,,}
    if [ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]; then
        get_cms
    fi
}

但无论我输入什么(无论正确或错误的值),它都不会再次调用函数,因为我只想允许这三个输入中的1个。

我已经尝试使用 ||,以及使用 [ var != value ] or [ var != value1 ] or [ var != value1 ],但都没有起作用。

有人能指点我正确的方向吗?


@triplee 我投票支持重新开放。我认为另一个问题 https://dev59.com/sWEh5IYBdhLWcg3wk0Oz 应该被关闭。这个问题表述更清晰,提出更早,并且回答更好。 - studgeek
1
@studgeek 感谢您的建议,我已经这样做了。 - tripleee
在我看来,重复的回答更好,因为它将case作为接受的答案,但我想这主要是个人口味问题。请注意,case可移植回原始Bourne shell,并且当然也适用于现代POSIX shell。也许还可以参考sh和bash之间的区别 - tripleee
1
谢谢。这个问题确实有一个答案。虽然它不是被接受的答案,但我个人总是会查看所有答案及其投票情况,而不仅仅是被接受的答案(因为后者只是一个人的意见,而投票则来自许多人)。 - studgeek
5个回答

178

如果主要意图是检查提供的值是否未在列表中找到,也许您可以通过BASH中内置的扩展正则表达式匹配来使用“等于波浪”运算符(参见此答案):

if ! [[ "$cms" =~ ^(wordpress|meganto|typo3)$ ]]; then get_cms ; fi

3
额外加分给更简洁的答案。我喜欢将!放在 [[ ]] 内,但两种方式都是正确的。 - foob.ar
非常感谢您的回答。帮助我解决了工作中的一个问题! - maoyi
因为某些原因,这对我不起作用。我正在使用(id=2015 | id=2016),如果变量在任何地方具有这些字符串之一,则希望IF执行,但它不起作用。 - Freedo

60

也许您应该在这种列表中使用case

case "$cms" in
  wordpress|meganto|typo3)
    do_your_else_case
    ;;
  *)
    do_your_then_case
    ;;
esac

我认为对于这样长的列表,这样更易读。

如果您仍然喜欢使用if,则可以用单括号两种方式实现:

if [ "$cms" != wordpress -a "$cms" != meganto -a "$cms" != typo3 ]; then
或者
if [ "$cms" != wordpress ] && [ "$cms" != meganto ] && [ "$cms" != typo3 ]; then

1
它运行得很好!顺便问一下,“do_your_then|else_case”文本提示是不是反了? :) - Aquarius Power
2
OP最初使用!=来表达他的条件,而我的版本中使用了case匹配(因此它实际上是使用=进行比较),因此需要反转。 - Alfe
1
优美的解决方案。使用这种情况是否存在任何陷阱? - Xiao
@Alfe,只要您没有使用-a版本(该版本在当前的test POSIX标准中被标记为过时),则进行比较的字符串内容根本不会产生任何影响。即使您的 $foo$bar或其他在更长的参数列表中可能与test有潜在意义的字符,[ "$foo" != "$bar" ]也是完全明确的。 - Charles Duffy
2
@Alfe,参见http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html中的`[OB XSI]`符号。如果您单击它,它将链接到一个定义:所描述的功能可能会在POSIX.1-2008的未来版本中被删除。严格符合POSIX应用程序和严格符合XSI应用程序不得使用过时的功能。我怀疑GNU coreutils或bash实际上不会删除它,但是由于引入的歧义,它在未来不再是POSIX强制执行的行为(有充分的理由)。 - Charles Duffy
显示剩余3条评论

56

改为:

if [ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]; then
say:
if [[ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]]; then

您可能还想参考条件语句


2
反复使用"$cms" !=并不是很DRY。这样做会在任何情况下留下错别字的余地(例如,"$csm" !=),而且你没有编译器来在shell中指出这一点。最好尝试避免重复变量名。(请参见我的答案以了解如何实现。) - Alfe
我不得不在末尾删除 ;,所以它变成了 ..."typo3"]] then,但除此之外,这个代码完全正常。 - Gharbad The Weak

16
根据@Renich的建议,您还可以使用扩展通配符进行模式匹配。因此,在bash比较中,您可以使用与在命令参数中匹配文件使用的相同模式(例如ls *.pdf)相同的模式。
对于您的特定情况,您可以执行以下操作。
if [[ "${cms}" != @(wordpress|magento|typo3) ]]
@ 表示“匹配给定的模式之一”。所以基本上这句话是说 cms 不等于 'wordpress' 或者 'magento' 或者 'typo3'。在正则表达式语法中,@ 类似于 ^(wordpress|magento|typo3)$
Mitch Frazier 在 Linux Journal 上有两篇关于这个主题的好文章,分别是 Bash 中的模式匹配Bash 扩展通配符
要了解更多关于扩展通配符的背景知识,请参阅 模式匹配(Bash 参考手册)

8

这是我的解决方案

if [[ "${cms}" != @(wordpress|magento|typo3) ]]; then

8
你的 extglob 中应该使用 @,而不是 +。否则,例如 wordpressmagentotypo3typo3 将会得到错误的结果。 - gniourf_gniourf
@gniourf_gniourf 我认为Edgar Grill的答案存在相同的问题,使用 ^ 可能会匹配任何以 "wordpress..." 开头的结果等。 - Jesse Nickles

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