第31章:流程控制:case 分支

本章中,我们继续学习流程控制。在第 28 章「读取键盘输入」中,我们构建了一个简单的菜单,并建立了基于用户选择而行动的逻辑。要做到这一点,我们使用了一系列的 if 命令去识别哪个选项被选择。这类逻辑结构在程序中频繁出现,所以许多程序语言(包括 shell)为多项选择提供了一种特殊的流控制机制。

case

bash,多项选择复合命令,名为 case。句法如下:

case word in
    [pattern [| pattern]...) commands ;;]...
esac

我们看一下第 28 章的 read-menu 程序,可以看到基于用户选择的逻辑。

#!/bin/bash

# read-menu: a menu driven system information program

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "
if [[ "$REPLY" =~ ^[0-3]$ ]]; then
    if [[ "$REPLY" == 0 ]]; then
        echo "Program terminated."
        exit
    fi
    if [[ "$REPLY" == 1 ]]; then
        echo "Hostname: $HOSTNAME"
        uptime
        exit
    fi
    if [[ "$REPLY" == 2 ]]; then
        df -h
        exit
    fi
    if [[ "$REPLY" == 3 ]]; then
        if [[ "$(id -u)" -eq 0 ]]; then
            echo "Home Space Utilization (All Users)"
            du -sh /home/*
        else
            echo "Home Space Utilization ($USER)"
            du -sh "$HOME"
        fi
        exit
    fi
else
    echo "Invalid entry." >&2
    exit 1
fi

使用 case,我们可以用更简单的语句替换这个逻辑。

#!/bin/bash

# case-menu: a menu driven system information program

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "

case "$REPLY" in
    0)  echo "Program terminated."
        exit
        ;;
    1)  echo "Hostname: $HOSTNAME"
        uptime
        ;;
    2)  df -h
        ;;
    3)  if [[ "$(id -u)" -eq 0 ]]; then
            echo "Home Space Utilization (All Users)"
            du -sh /home/*
        else
            echo "Home Space Utilization ($USER)"
            du -sh "$HOME"
        fi
        ;;
    *)  echo "Invalid entry" >&2
        exit 1
        ;;
esac

case 命令观察 word 的值,在我们的例子中,就是 REPLY 变量的值,然后试图去和指定的模式(patterns)匹配。当遇到匹配时,会执行与指定模式关联的命令(commands)。当遇到一个匹配后,就不会再尝试更多的匹配了。

模式

用于 case 的模式与用于路径扩展的模式相同。模式由 ")" 字符结束。表 31-1 列出了一些合法模式的示例。

表 31-1: case 模式示例

模式

描述

a)

如果 word 等于 "a",则匹配。

[[:alpha:]])

如果 word 是单个字母字符,则匹配。

???)

如果 word 的长度是三个字符,则匹配。

*.txt)

如果 word 以 ".txt" 结尾,则匹配。

*)

匹配任意值的 word。实践中,将其作为在 case 命令的最后一个模式,以捕获之前几个模式以外的任意值,是很好的,换言之,要捕获的是任何可能的非法值。

这里是一个可用的模式示例:

#!/bin/bash

read -p "enter word > "

case "$REPLY" in
    [[:alpha:]])  echo "is a single alphabetic character." ;;
    [ABC][0-9])   echo "is A, B, or C followed by a digit." ;;
    ???)          echo "is three characters long." ;;
    *.txt)        echo "is a word ending in '.txt'" ;;
    *)            echo "is something else." ;;
esac

还可以用竖线字符作为分隔符,组合多个模式。这就创建了一个「或」的条件模式。这在处理大小写字符时就很有用。例如:

#!/bin/bash

# case-menu: a menu driven system information program

clear
echo "
Please Select:

A. Display System Information
B. Display Disk Space
C. Display Home Space Utilization
Q. Quit
"
read -p "Enter selection [A, B, C or Q] > "

case "$REPLY" in
    q|Q) echo "Program terminated."
         exit
         ;;
    a|A) echo "Hostname: $HOSTNAME"
         uptime
         ;;
    b|B) df -h
         ;;
    c|C) if [[ "$(id -u)" -eq 0 ]]; then
             echo "Home Space Utilization (All Users)"
             du -sh /home/*
         else
             echo "Home Space Utilization ($USER)"
             du -sh "$HOME"
         fi
         ;;
    *)   echo "Invalid entry" >&2
         exit 1
         ;;
esac

这里,我们修改了 case-menu 程序,用字母代替了数字作为菜单选项。注意新的模式是如何允许输入任意的大小写字符的。

执行多个行为

在 4.0 版的 bash 之前,case 仅允许在一个成功匹配时执行单个行为。在成功匹配之后,该命令就终结了。这里来看一个测试字符的脚本:

#!/bin/bash

# case4-1: test a character

read -n 1 -p "Type a character > "
echo
case "$REPLY" in
    [[:upper:]]) echo "'$REPLY' is upper case." ;;
    [[:lower:]]) echo "'$REPLY' is lower case." ;;
    [[:alpha:]]) echo "'$REPLY' is alphabetic." ;;
    [[:digit:]]) echo "'$REPLY' is a digit." ;;
    [[:graph:]]) echo "'$REPLY' is a visible character." ;;
    [[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;
    [[:space:]]) echo "'$REPLY' is a whitespace character." ;;
    [[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;
esac

运行该脚本后产生了这个:

[me@linuxbox ~]$ case4-1
Type a character > a
'a' is lower case.

这个脚本在大多数情况下能顺利工作,但是如果一个字符匹配了多于一类 POSIX 字符类的话,就失效了。例如,字符 "a" 既是小写字母,也是一个十六进制的数字。在早于 4.0 版本的 bash 中,无法让 case 匹配多于一个测试。现代版本的 bash 加了 ;;& 标记到每个行为的末尾,就可以做到这一点了:

#!/bin/bash

# case4-2: test a character

read -n 1 -p "Type a character > "
echo
case "$REPLY" in
    [[:upper:]]) echo "'$REPLY' is upper case." ;;&
    [[:lower:]]) echo "'$REPLY' is lower case." ;;&
    [[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]]) echo "'$REPLY' is a digit." ;;&
    [[:graph:]]) echo "'$REPLY' is a visible character." ;;&
    [[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac

当运行脚本时,就得到如下结果:

[me@linuxbox ~]$ case4-2
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

额外的 ;;& 句法允许 case 可以继续下一个测试,而不是简单的终结命令。

总结

case 命令是我们编程技巧中的一款便利工具。我们会在下一章中看到,它是处理某些类型问题的完美工具。

扩展阅读

Last updated

Was this helpful?