前言
如同大部分的程式語言,shell script 的執行順序是由上而下、依序執行。控制結構則是用來改變電腦程式的運行順序。本文介紹 shell script 的控制結構。
布林表達式
布林表達式用來判斷特定條件的真偽。在控制結構中,用來決定該結構的起始或終止條件。Shell script 的布林表達式有兩種語法:
test expression
[ expression ]
將 expression
的部分填入條件即可。
if
if
是基本的選擇控制結構。其使用方式如以下虛擬碼:
if condition;
then
# Run commands here.
fi
只有在 condition
為真時,才會執行 then
區塊內的程式碼。使用 if
的反向字 fi
當成 if
敘述的結尾是 shell script 的特色。
除了單一的 if
敘述外,也可以加入選擇性的二元敘述:
if condition;
then
# Run commands here only if condition is true.
else
# Run commands here only if condition is false.
fi
當 condition
為真時,執行 then
區塊內的程式碼;反之,則執行 else
區塊內的程式碼。
除了單一敘述和二元敘述外,還可以使用選擇性的多元敘述:
if condition_a;
then
# Run commands here only if condition_a is true.
elif condition_b;
then
# Run commands here only if condition_b is true.
else
# Run commands here only if none of the above conditions is true.
fi
當 condition_a
為真時,執行第一個 then
區塊內的程式碼;當 condition_b
為真時,則執行第二個 then
區塊內的程式碼;若兩者皆為偽,則執行 else
區塊內的程式碼。
注意 else
區塊是選擇性的,不需要時可省略不寫。
以下是使用 if
的實際程式碼片段:
if true;
then
echo "It's true";
fi
另一種寫法是將 if
和 then
寫在同一行:
if true; then
echo "It's true";
fi
這兩種寫法僅是風格的差異,讀者擇一使用即可。
以下 shell script 用兩個 if
敘述來檢視系統上的 Objective-C 環境是否存在:
#!/bin/sh
if ! gcc --version 2>/dev/null 1>&2;
then
echo "GCC is not installed on your system" >&2;
exit 1;
fi
gcc_ver=$(gcc --version | head -n1 | grep -oP "\d.\d.\d$");
if [ ! -d "/usr/lib/gcc/x86_64-linux-gnu/${gcc_ver}/include/objc" ];
then
echo "Objective-C runtime is not installed on your system" >&2;
exit 1;
fi
exit 0;
第一個 if
敘述檢查 GCC 是否存在。檢視的方式為執行 gcc --version
指令,以確認 GCC 可用。
第二個 if
敘述檢查 Objective-C 運行期函式庫是否存在。由於函式庫非執行檔,故改檢查標頭檔的目錄是否存在。
在非蘋果平台,常用的 Objetive-C 編譯器是 GCC 而非 Clang,故針對 GCC 來檢查。
case
case
是另一個選擇控制結構,其虛擬碼如下:
case $value in
pattern_a)
# Run code here if $value fits pattern_a
;;
pattern_b)
# Run code here if $value fits pattern_b
;;
*)
# Run default code here
;;
esac
case
敘述會根據 value
的值選擇分支。當 value
符合 pattern_a
時,執行第一個分支。當 value
符合 pattern_b
時,執行第二個分支。當 value
不符合任何模式時,則執行預設分支。
case
的分支都是選擇性的,包括預設分支。若不需要可省略。
注意 case
敘述以反字 esac
結尾,這是 shell script 的特色。
以下實例用來檢查系統上可用的 Objective-C 編譯器:
#!/bin/sh
if ! command -v ${CC:=gcc} 2>/dev/null 1>&2;
then
echo "No C compiler on your system" >&2;
exit 1;
fi
case $CC in
*gcc*|*clang*)
true; # pass.
;;
*)
echo "Not a valid Objective-C compiler: $CC" >&2;
exit 1;
;;
esac
exit 0;
if
敘述用來檢查環境變數 CC
。若該變數為空,則填入預設值 gcc
。使用 command
指令來確認 CC
所代表的 C 編譯器是否存在。
case
敘述則用來檢查該 C 編譯器是否也是合法的 Objective-C 編譯器。目前可用的 Objective-C 編譯器為 GCC 和 Clang,所以針對這兩種編譯器去檢查即可。
由於 shell script 的 case
敘述具有過濾字串的功能,和 if
不完全等義。有關 case
所用的樣式比對語法請看此處。
while
while
是不定次數的迭代控制結構。其虛擬碼如下:
while condition;
do
# Run code here while `condition` is true.
done
只要 condition
為真,就會執次一次 while
區塊內的程式碼,然後再重新檢查一次 condition
。實際上寫 while
敘述時,要在區塊內寫終止該迴圈的指令,否則就變無窮迴圈了。
這裡用 while
迴圈寫一個 shell 版本的終極密碼遊戲:
#!/bin/sh
max=100;
min=1;
answer=$(shuf -i $min-$max -n 1);
while true;
do
printf "Please input a number between %d and %d: " $min $max;
read -r guess;
echo "$guess" | grep -q -P "^[+-]?\d+$"
if [ 0 -ne $? ];
then
echo "Not an integer" >&2;
continue;
fi
if [ "$guess" -lt "$min" ] || [ "$guess" -gt "$max" ];
then
echo "Invalid number" >&2;
continue;
fi
if [ "$guess" -gt "$answer" ];
then
echo "Too large";
max=$guess;
elif [ "$guess" -lt "$answer" ];
then
echo "Too small";
min=$guess;
else
echo "You win";
break;
fi
done
exit 0;
一開始先用 shuf(1)
指令在 min
及 max
間選出一個任意數,將該數存到 answer
。由於這個過程是隨機的,不會預先知道 answer
的值。
整個遊戲過程寫在一個大的 while
迴圈中。在遊戲結束之前,此遊戲會不斷地玩下去,所以直接用 true
指令當成該迴圈的條件。
遊戲一開始先以提示文字 (prompt) 請使用者輸入特定範圍的數字。由於提示文字不要換行,所以用 printf(1)
取代 echo(1)
。前者對文字輸出有比較精細的操作。使用 read(1)
指令讀入使用者的輸入。
輸入的資料不一定是合理的數字,所以要進行必要的檢查。第一次檢查用 grep(1)
來確認輸入是否為整數。在 grep
中加上 -P
參數就可以用 Perl 的常規表示式,會比 grep
原本的常規表示式來得好用。第二次檢查則是確認 guess
在 min
及 max
的範圍內。
最後就是比對 guess
和 answer
,確認兩者是否相等。當兩者相等,代表使用者猜到正確的數字,就以 break
指令結束遊戲迴圈。若兩者不相對,則縮小數字的範圍,這樣會比較容易遊玩。
until
until
同樣是不定次數的迭代控制結構。其虛擬碼如下:
until condition;
do
# Run code here until `condition` is false.
done
until
迴圈會反覆運行,直到 condition
為偽才停止。由於 until
隱含著反向條件,較 while
難用,只有在語義符合時才會使用。
將先前的終極密碼遊戲改用 until
迴圈改寫如下:
#!/bin/sh
max=100;
min=1;
answer=$(shuf -i $min-$max -n 1);
game_over=false
until $game_over;
do
printf "Please input a number between %d and %d: " $min $max;
read -r guess;
echo "$guess" | grep -q -P "^[+-]?\d+$"
if [ 0 -ne $? ];
then
echo "Not an integer" >&2;
continue;
fi
if [ "$guess" -lt "$min" ] || [ "$guess" -gt "$max" ];
then
echo "Invalid number" >&2;
continue;
fi
if [ "$guess" -gt "$answer" ];
then
echo "Too large";
max=$guess;
elif [ "$guess" -lt "$answer" ];
then
echo "Too small";
min=$guess;
else
echo "You win";
game_over=true;
fi
done
exit 0;
為了因應 until
迴圈,這裡多用個變數 game_over
,讓 until
迴圈用起來更自然。除了遊戲迴圈的終止條件外,其他的指令和前一節的程式雷同,故不重覆說明。
for
for
是有特定次數的迭代控制結構。其虛擬碼如下:
for var in list;
do
# Run code one by one.
done
shell script 沒有真正的串列,這裡的 list
僅是以分隔符隔開的字串。預設的分隔符為空白。
以下 shell script 用於 Debian 相容系統,用來自動安裝 GNUstep 的相依函式庫:
#!/bin/sh
gnustep_debs=$(cat << DEBS
gobjc gobjc++ libjpeg-dev libtiff-dev libpng-dev libicns-dev
libxml2-dev libxslt-dev libgnutls-dev libffi-dev libicu-dev
libcairo2-dev libxft-dev libavahi-client-dev flite-dev libxt-dev
libportaudio-dev wmaker portaudio19-dev make cmake gnutls-dev
DEBS
);
for deb in $gnustep_debs;
do
if [ "$(id -u)" -eq 0 ];
then
apt-get install -y "$deb";
else
sudo apt-get install -y "$deb";
fi
done;
exit 0;
gnustep_debs
是一個跨越多行的字串,字串內容即為套件名稱。各個套件名稱間以空白隔開。
在 for
迴圈每次的迭代中,會取出一個套件名稱,存到變數 deb
中。由於此 shell script 的目標是全自動安裝套件,所以在執行 apt-get
指令時搭配 -y
參數,以略過詢問使用者的過程。