Shell基礎之控制流結構
一、控制結構
幾乎所有的腳本里都有某種流控制結構,很少有例外。流控制是什么?假定有一個腳本,包含下列幾個命令:
#!/bin/sh
# make a directory
mkdir /home/dave/mydocs
# copy all doc files
cp *.docs /home/dave/docs
# delete all doc files
rm *.docs
上述腳本問題出在哪里?如果目錄創建失敗或目錄創建成功文件拷貝失敗,如何處理?這里需要從不同的目錄中拷貝不同的文件。必須在命令執行前或最后的命令退出前決定處理方法。shell會提供一系列命令聲明語句等補救措施來幫助你在命令成功或失敗時,或需要處理一個命令清單時采取正確的動作。這些命令語句大概分兩類:
1、循環和流控制
-
if 語句
提供條件測試。測試可以基於各種條件。例如文件的權限、長度、數值或字符串的比較。這些測試返回值或者為真(0),或者為假(1)。基於此結果,可以進行相關操作。在講到條件測試時已經涉及了一些測試語法。 -
case語句
允許匹配模式、單詞或值。一旦模式或值匹配,就可以基這個匹配條件作其他聲明。
2、循環
循環或跳轉是一系列命令的重復執行過程,本書提到了3種循環語句:
-
for 循環
每次處理依次列表內信息,直至循環耗盡。 -
Until 循環
此循環語句不常使用, until循環直至條件為真。條件部分在循環末尾部分。 -
While 循環
while循環當條件為真時,循環執行,條件部分在循環頭。
流控制語句的任何循環均可嵌套使用,例如可以在一個for循環中嵌入另一個for循環。
二、實例講解
現在開始講解循環和控制流,並舉一些腳本實例。
從現在起,腳本中語句使用LINUX或BSD版本,也就是說使用echo方法echo -e -n,意即從echo結尾中下一行執行命令。
1、grep輸出檢查
不必拘泥於變量或數值測試,也可以測知系統命令是否成功返回。對grep使用if語句找出,grep是否成功返回信息。下面的例子中grep用於查看Dave是否在數據文件data.file中,注意’Dave>‘用於精確匹配。
[root@localhost ~]# cat grepif.sh
#!/bin/sh
# grepif.sh
if grep 'Dave\>' data.file > /dev/null 2>&1
then
echo "Great Dave is in the file"
else
echo "No Dave is not in the file"
fi
[root@localhost ~]# ./grepif.sh
No Dave is not in the file
2、用變量測試grep輸出
正像前面看到的,可以用grep作字符串操作。下面的腳本中,用戶輸入一個名字列表,grep在變量中查找,要求其包含人名Peter
[root@localhost ~]# cat grepstr.sh
#!/bin/sh
# grepstr
echo -n "Enter a list of names:"
read list
if echo $list | grep "Peter" > /dev/null 2>&1
then
echo "Peter is here"
# could do some processing here...
else
echo "Peter's not in the list. No comment!"
fi
[root@localhost ~]# ./grepstr.sh
Enter a list of names:John Louise Peter James
Peter is here
3、文件拷貝輸出檢查
下面測試文件拷貝是否正常,如果cp命令並沒有拷貝文件myfile到myfile.bak,則打印錯誤信息。注意錯誤信息中
basename $0
打印腳本名。如果腳本錯誤退出,一個好習慣是顯示腳本名並將之定向到標准錯誤中。用戶應該知道產生錯誤的腳本名。
[root@localhost ~]# cat ifcp.sh
#!/bin/sh
# ifcp.sh
if cp myfile myfile.bak; then
echo "good copy"
else
echo "`basename $0`: error could not copy the file" >&2
fi
[root@localhost ~]# ./ifcp.sh
cp: cannot stat `myfile': No such file or directory
ifcp.sh: error could not copy the file
注意,文件可能沒找到,系統也產生本身的錯誤信息,這類錯誤信息可能與輸出混在一起。既然已經顯示系統錯誤信息獲知腳本失敗,就沒必要顯示兩次。要去除系統產生的錯誤和系統輸出,只需簡單的將標准錯誤和輸出重定向即可。修改腳本為: >/dev/null 2>&1。
[root@localhost ~]# cat ifcp.sh
#!/bin/sh
# ifcp.sh
if cp myfile myfile.bak > /dev/null 2>&1; then
echo "good copy"
else
echo "`basename $0`: error could not copy the file" >&2
fi
[root@localhost ~]# ./ifcp.sh
ifcp.sh: error could not copy the file
上面當中>/dev/null表示任何標准輸出都定向到那個無盡的“黑洞”/de/null中,然后2>&1表示錯誤輸出也是到/dev/null中,&1表示前面的那個/dev/null,腳本運行時,所有輸出包括錯誤重定向至系統垃圾堆。
4、當前目錄測試
當運行一些管理腳本時,可能要在根目錄下運行它,特別是移動某種全局文件或進行權限改變時。一個簡單的測試可以獲知是否運行在根目錄下。下面腳本中變量DIRECTORY使用當前目錄的命令替換操作,然后此變量值與” / “字符串比較( /為根目錄)。如果變量值與字符串不等,則用戶退出腳本,退出狀態為1意味錯誤信息產生。
[root@localhost ~]# cat ifpwd.sh
#!/bin/sh
# ifpwd.sh
DIRECTORY=`pwd`
# grab the current dirctory
if [ "$DIRECTORY" != "/" ]; then
# is it the root directory ?
# no, the direct output to standard error, which is the screen
# by default.
echo "You need to be in the root directory no $DIRECTORY to run
this script" >&2
# exit with a value of 1, an error
exit 1
fi
[root@localhost ~]# ./ifpwd.sh
You need to be in the root directory no /root to run
this script
5、文件權限測試
可以用i f語句測試文件權限,下面簡單測試文件test.txt是否被設置到變量LOGNAME,測試test.txt文件是否具有寫的權限。下面的腳本先建立一個test.txt的空白文檔,列出它的相關權限。然后執行腳本測試其是否可以寫入,然后顯示相關信息。
[root@localhost ~]# touch test.txt
[root@localhost ~]# ls -l test.txt
-rw-r--r-- 1 root root 0 Nov 21 15:21 test.txt
[root@localhost ~]# chmod u+x ifwr.sh
[root@localhost ~]# cat ifwr.sh
#!/bin/sh
# ifwr.sh
LOGFILE=test.txt
echo $LOGFILE
if [ ! -w "$LOGFILE" ]; then
echo " You cannot write to $LOGFILE" >&2
else
echo " You can write to $LOGFILE" >&2
fi
[root@localhost ~]# ./ifwr.sh
test.txt
You can write to test.txt
6、測試傳遞到腳本中的參數
if語句可用來測試傳入腳本中參數的個數。使用特定變量$#,表示調用參數的個數。可以測試所需參數個數與調用參數個數是否相等。以下測試確保腳本有三個參數。如果沒有,則返回一個可用信息到標准錯誤,然后代碼退出並顯示退出狀態。如果參數數目等於3,則顯示所有參數。
[root@localhost ~]# cat ifparam.sh
#!/bin/sh
# ifparam
if [ $# -lt 3 ]; then
# less than 3 parameters called, echo a usage message and exit
# 如果少於三個參數則顯示使用的信息,然后退出。
echo "Usage: `basename $0`arg1 arg2 arg3" >&2
exit 1
fi
# good, received 3 params, let's echo them
# 好,現在接受了三個參數,讓我們開始顯示他們
echo "arg1: $1"
echo "arg2: $2"
echo "arg3: $3"
[root@localhost ~]# ./ifparam.sh cup medal
Usage: ifparam.sharg1 arg2 arg3
[root@localhost ~]# ./ifparam.sh cup medal trophy
arg1: cup
arg2: medal
arg3: trophy
從上面的運行信息可以看出,如果只傳入兩個參數,則顯示一可用信息,然后腳本退出。只有正確傳入了三個參數了,才顯示所有的參數然后退出。
7、決定腳本是否為交互模式
有時需要知道腳本運行是交互模式(終端模式)還是非交互模式(cron或at)。腳本也許需要這個信息以決定從哪里取得輸入以及輸出到哪里,使用test命令並帶有-t選項很容易確認這一點。如果test返回值為1,則為交互模式。假如我是在一個終端下運行下面這個腳本。
[root@localhost ~]# cat ifinteractive.sh
#!/bin/sh
# ifinteractive.sh
if [ -t ]; then
echo "We are interactive with a terminal"
else
echo "We must be running from some background process probably
cron or at"
fi
[root@localhost ~]# ./ifinteractive.sh
We are interactive with a terminal
8、變量設置測試
下面的例子測試環境變量EDITOR是否已設置。如果EDITOR變量為空,將此信息通知用戶。如果已設置,在屏幕上顯示編輯類型。
[root@localhost ~]# cat ifeditor.sh
#!/bin/sh
# ifeditor.sh
if [ -z $EDITOR ]; then
# the variable has not been set
# 變量沒有設置
echo "Your EDITOR environment is not set"
else
# let's see what it is
# 如果設置了,讓我們來看看它到底是什么
echo "Using $EDITOR as the default editor"
fi
[root@localhost ~]# ./ifeditor.sh
Your EDITOR environment is not set
9、將腳本參數傳入系統命令
可以向腳本傳遞位置參數,然后測試變量。這里,如果用戶在腳本名字后鍵入目錄名,腳本將重設$1特殊變量為一更有意義的名字。即DIRECTORY。這里需測試目錄是否為空,如果目錄為空,ls -A將返回空,然后對此返回一信息。
# ifdirec.sh
# assigning $1 to DIRECTORY variable
DIRECTORY=$1
if [ "`ls -A $DIRECTORY`" == "" ]; then
# if it's an empty string, then it's empty
echo "$DIRECTORY is indeed empty"
else
# otherwise it is not
echo "$DIRECTORY is not empty"
fi
也可以使用下面的腳本替代上面的例子並產生同樣的結果
[root@localhost ~]# cat ifdirec2.sh
#!/bin/sh
# ifdirec2
DIRECTORY=$1
if [ -z "`ls -A $DIRECTORY`" ]
then
echo "$DIRECTORY is indeed empty"
else
echo "$DIRECTORY is not empty"
fi
10、null命令用法
到目前為止,條件測試已經講完了then和else部分,有時也許使用者並不關心條件為真或為假。不幸的是if語句各部分不能為空—一些語句已經可以這樣做。為解決此問題, shell提供了:
空命令
。空命令永遠為真(也正是預想的那樣)。回到前面的例子,如果目錄為空,可以只在then部分加入命令。
[root@localhost ~]# cat ifdirectory.sh
#!/bin/sh
# ifdirectory.sh
DIRECTORY=$1
if [ "`ls -A $DIRECTORY`" == "" ]
then
echo "$DIRECTORY is indeed empty"
else :
# do nothing
fi
[root@localhost ~]# ./ifdirectory.sh testd
testd is indeed empty