case 语句中的 AND 运算符

case 语句中的 AND 运算符

我有以下代码。

read -p "Enter a word: " word

case $word in
  [aeiou]* | [AEIOU]*)
          echo "The word begins with a vowel." ;;
  [0-9]*)
          echo "The word begins with a digit." ;;
  *[0-9])
          echo "The word ends with a digit." ;;
  [aeiou]* && [AEIOU]* && *[0-9])
          echo "The word begins with vowel and ends with a digit." ;;
   ????)
          echo "You entered a four letter word." ;;
   *)
          echo "I don't know what you've entered," ;;
esac

当我运行这个时:

Enter a word: apple123
case2.sh: line 10: syntax error near unexpected token `&&'
case2.sh: line 10: `  [aeiou]* && [AEIOU]* && *[0-9])'

看起来 case 语句不支持 AND 运算符,而且我相信上面 case 语句中的 && 运算符在逻辑上不正确。

我知道我们可以使用 if else 来检查输入是否以元音和数字开头。但我很好奇 case 是否有任何内置函数,如 AND 运算符。

答案1

你是对的标准定义case模式中不允许使用 AND 运算符。您也正确地尝试说“以小写元音开头并且以大写元音开头”不会匹配任何内容。另请注意,您的模式和解释颠倒了数字测试的开始/结束 - 使用[0-9]*将匹配单词的模式开始带数字,不结尾与一个数字。

一种方法是将您的测试组合成相同的模式,首先是最严格的:

case $word in
  ([AaEeIiOoUu]??[0-9]) echo it is four characters long and begins with a vowel and ends with a digit;;
  ([AaEeIiOoUu]*[0-9])  echo it is not four characters long begins with a vowel and ends with a digit;;
# ...
esac

另一种(冗长!)方法是嵌套您的case语句,每次都建立适当的响应。它以元音开头吗?是还是不是?现在,它以数字结尾吗,是还是否?这很快就会变得笨拙,并且维护起来很烦人。

另一种方法是使用一系列case语句来构建适用语句的字符串(或数组);如果您想*提供“负面”反馈(“词确实不是以元音开头”等)。

result=""
case $word in
  [AaEeIiOoUu]*)
          result="The word begins with a vowel." ;;
esac

case $word in
  [0-9]*)
          result="${result} The word begins with a digit." ;;
esac

case $word in
  *[0-9])
          result="${result} The word ends with a digit." ;;
esac

case $word in
   ????)
          result="${result} You entered four characters." ;;
esac

printf '%s\n' "$result"

举些例子:

$ ./go.sh
Enter a word: aieee
The word begins with a vowel.
$ ./go.sh
Enter a word: jeff42
 The word ends with a digit.
$ ./go.sh
Enter a word: aiee
The word begins with a vowel. You entered four characters.
$ ./go.sh
Enter a word: 9arm
 The word begins with a digit. You entered four characters.
$ ./go.sh
Enter a word: arm9
The word begins with a vowel. The word ends with a digit. You entered four characters.

或者,casebash 扩展了语句的语法允许选择多个模式,如果您以以下方式结束模式;;&

shopt -s nocasematch
case $word in
  [aeiou]*)
          echo "The word begins with a vowel." ;;&
  [0-9]*)
          echo "The word begins with a digit." ;;&
  *[0-9])
          echo "The word ends with a digit." ;;&
   ????)
          echo "You entered four characters." ;;
esac

请注意,我删除了*包罗万象的模式,因为当以这种方式穿过模式时,它会匹配任何内容。 Bash 还有一个名为 的 shell 选项nocasematch,我在上面设置了该选项,它可以实现模式的不区分大小写的匹配。这有助于减少冗余——我删除了| [AEIOU]*模式的一部分。

举些例子:

$ ./go.sh
Enter a word: aieee
The word begins with a vowel.
$ ./go.sh
Enter a word: jeff42
The word ends with a digit.
$ ./go.sh
Enter a word: aiee
The word begins with a vowel.
You entered four characters.
$ ./go.sh
Enter a word: 9arm
The word begins with a digit.
You entered four characters.
$ ./go.sh
Enter a word: arm9
The word begins with a vowel.
The word ends with a digit.
You entered four characters.

答案2

为了完整起见,虽然case|OR 运算符,但它没有 AND 运算符,但如果使用带有扩展 glob 运算符(ksh、zsh、bash)的 shell,则可以在图案句法:

  • ksh93@(x&y&z)运算符:

      case $string in
        ( @({12}(?)&~(i:[aeiou]*)&*[0123456789]) )
          echo is 12 characters long AND starts with a vowel AND ends in a decimal
      esac
    
  • zsh(使用~(AND-NOT)与^(NOT)相结合):x~^y~^z

      set -o extendedglob
      case $string in
        ( ?(#c12)~^(#i)[aeiou]*~^*[0-9] )
          echo is 12 characters long AND starts with a vowel AND ends in a decimal
      esac
    
  • ksh88, bash, 使用 OR ( !(!(x)|!(y)|!(z)))的双重否定

      shopt -s extglob # bash only
      case $string in
        ( !(!(????????????)|!([aAeEıiIİoOuU]*)|!(*[0123456789])) )
          echo is 12 characters long AND starts with a vowel AND ends in a decimal
      esac
    

在任何情况下,请记住,除了 zsh 中的范围始终基于代码点值之外,类似的范围[0-9]不能在 POSIX/C 语言环境之外可靠地使用(因此是[0123456789]上面的替代方案)。

ksh93 和 zsh 的不区分大小写的匹配运算符 (~(i)(#i)) 遵循区域设置以进行区分大小写的比较。例如,在 GNU 系统的土耳其语言环境中,(#i)[aeiou]将匹配İ,但不会匹配I(因为存在大写字母iİ。为了获得一致的结果(无论语言环境如何),您可能需要对所有可能的值进行硬编码,而不是像 ksh88/bash 方法中那样。

答案3

通常便携的在 case 语句中实现 AND 的解决方案是连接布尔值:

case $A$B in
    11) echo "Both conditions are true";;
    1*) echo "Condition A is true";;
    *1) echo "Condition B is true";;
    00) echo "Both conditions are false";;
     *) echo "There is an unexpected error";;
esac

对于您的用例:

printf "Enter a word: "; read word

A=0 B=0 C=0

case $word in    ( [aeiouAEIOU]* ) A=1;; esac
case $word in    ( *[0-9]        ) B=1;; esac
case $word in    ( ????          ) C=1;; esac

case $A$B$C in
  111)     echo "Four letters that start with a vowel and end with a digit" ;;
  11*)     echo "The word begins with a vowel AND ends with a digit."       ;;
  1* )     echo "The word begins with a vowel."                             ;;
  *1?)     echo "The word ends with a digit."                               ;;
   *1)     echo "The word is four letters long"                             ;;
    *)     echo "I don't understand what you've entered,"                   ;;
esac

每个布尔选项都使用便携式外壳。您可以;;&在 bash 或zsh 中使用;|。遗憾的是 ksh 没有这样的选项。

设置布尔值的另一种方法(至少在某些 shell 中:ksh、bash、zsh)是:

[[ $word ==   [aeiouAEIOU]* ]] && A=1 || A=0
[[ $word ==   *[0-9]        ]] && B=1 || B=0
[[ $word ==   ????          ]] && C=1 || c=0

相关内容