在這一章中,我們將繼續看一下程序的流程控制。在第28章中,我們構建了一些簡單的菜單并創建了用來 應對各種用戶選擇的程序邏輯。為此,我們使用了一系列的 if 命令來識別哪一個可能的選項已經被選中。 這種類型的構造經常出現在程序中,出現頻率如此之多,以至于許多編程語言(包括 shell) 專門為多選決策提供了一種流程控制機制。
## case
Bash 的多選復合命令稱為 case。它的語法規則如下所示:
~~~
case word in
[pattern [| pattern]...) commands ;;]...
esac
~~~
如果我們看一下第28章中的讀菜單程序,我們就知道了用來應對一個用戶選項的邏輯流程:
~~~
#!/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 命令檢查一個變量值,在我們這個例子中,就是 REPLY 變量的變量值,然后試圖去匹配其中一個具體的模式。 當與之相匹配的模式找到之后,就會執行與該模式相關聯的命令。若找到一個模式之后,就不會再繼續尋找。
## 模式
這里 case 語句使用的模式和路徑展開中使用的那些是一樣的。模式以一個 “)” 為終止符。這里是一些有效的模式。
表32-1: case 模式實例
| 模式 | 描述 |
|------|-------|
| a) | 若單詞為 “a”,則匹配 |
| [[:alpha:]]) | 若單詞是一個字母字符,則匹配 |
| ???) | 若單詞只有3個字符,則匹配 |
| *.txt) | 若單詞以 “.txt” 字符結尾,則匹配 |
| *) | 匹配任意單詞。把這個模式做為 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 命令是我們編程技巧口袋中的一個便捷工具。在下一章中我們將看到, 對于處理某些類型的問題來說,case 命令是一個完美的工具。
## 拓展閱讀
* Bash 參考手冊的條件構造一節詳盡的介紹了 case 命令:
[http://tiswww.case.edu/php/chet/bash/bashref.html#SEC21](http://tiswww.case.edu/php/chet/bash/bashref.html#SEC21)
* 高級 Bash 腳本指南提供了更深一層的 case 應用實例:
[http://tldp.org/LDP/abs/html/testbranch.html](http://tldp.org/LDP/abs/html/testbranch.html)
- 第一章:引言
- 第二章:什么是shell
- 第三章:文件系統中跳轉
- 第四章:研究操作系統
- 第五章:操作文件和目錄
- 第六章:使用命令
- 第七章:重定向
- 第八章:從shell眼中看世界
- 第九章:鍵盤高級操作技巧
- 第十章:權限
- 第十一章:進程
- 第十二章:shell環境
- 第十三章:VI簡介
- 第十四章:自定制shell提示符
- 第十五章:軟件包管理
- 第十六章:存儲媒介
- 第十七章:網絡系統
- 第十八章:查找文件
- 第十九章:歸檔和備份
- 第二十章:正則表達式
- 第二十一章:文本處理
- 第二十二章:格式化輸出
- 第二十三章:打印
- 第二十四章:編譯程序
- 第二十五章:編寫第一個shell腳本
- 第二十六章:啟動一個項目
- 第二十七章:自頂向下設計
- 第二十八章:流程控制 if分支結構
- 第二十九章:讀取鍵盤輸入
- 第三十章:流程控制 while/until 循環
- 第三十一章:疑難排解
- 第三十二章:流程控制 case分支
- 第三十三章:位置參數
- 第三十四章:流程控制 for循環
- 第三十五章:字符串和數字
- 第三十六章:數組
- 第三十七章:奇珍異寶