Unix系列shell程序編寫從入門到精通(下)

Until語句
While語句中,只要某條件爲真,則重複執行循環代碼,until語句正好同while相反,該語句使循環代碼重複執行,直到遇到某一條件爲真才中止。

Until語句的結構以下:
until command
do
command
command
… …
done

能夠用until語句替換上面備份程序的while語句,完成一樣的功能:

until [ $ANS != Y -a $ANS != y ]

for 循環
在介紹for循環以前,咱們要學個很是有用的unix命令:shift。咱們知道,對於位置變量或命令行參數,其個數必須是肯定的,或者當Shell程 序不知道其個數時,能夠把全部參數一塊兒賦值給變量$*。若用戶要求Shell在不知道位置變量個數的狀況下,還能逐個的把參數一一處理,也就是在$1後 爲$2,在$2後面爲$3等。在 shift命令執行前變量$1的值在shift命令執行後就不可用了。

示例以下:

#測試shift命令(x_shift.sh)
until [ $# -eq 0 ]
do
echo "第一個參數爲: $1 參數個數爲: $#"
shift
done
執行以上程序x_shift.sh:
$./x_shift.sh 1 2 3 4

結果顯示以下:

第一個參數爲: 1 參數個數爲: 3
第一個參數爲: 2 參數個數爲: 2
第一個參數爲: 3 參數個數爲: 1
第一個參數爲: 4 參數個數爲: 0

從上可知shift命令每執行一次,變量的個數($#)減一,而變量值提早一位,下面代碼用until和shift命令計算全部命令行參數的和。

#shift上檔命令的應用(x_shift2.sh)
if [ $# -eq 0 ]
then
echo "Usage:x_shift2.sh 參數"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum=`expr $sum + $1`
shift
done
echo "sum is: $sum"

執行上述程序:

$x_shift2.sh 10 20 15

其顯示結果爲:

45

shift命令還有另一個重要用途,Bsh定義了9個位置變量,從$1到$9,這並不意味着用戶在命令行只能使用9個參數,藉助shift命令能夠訪問多於9個的參數。

Shift命令一次移動參數的個數由其所帶的參數指定。例如當shell程序處理完前九個命令行參數後,可使用shift 9命令把$10移到$1。

在熟悉了shift命令後,咱們一塊兒看看,Bsh程序中很是有用的for循環語句,這種循環同上面說的while和until循環不一樣,for語句中的循環是否執行並不禁某個條件的真和假來決定,決定for循環是否繼續的條件是參數表中是否還有未處理的參數。

For語句的結構以下:

for variable in arg1 arg2 … argn
do
command
command
… …
done

下面是for循環的簡單例子:

for LETTER in a b c d
do
echo $LETTER
done

程序執行結果以下:

a
b
c
d

在上面計算參數和的例子中,咱們能夠用for循環,實現以下:

#測試 for 程序(x_for.sh)

if [ $# -eq 0 ]
then
echo "Usage:x_for.sh 參數… …"
exit 1
fi
sum=0
for I in $*
do
sum=`expr $sum + $I`
done
echo "sum is: $sum"

中斷循環指令
在程序循環語句中,咱們有時候但願遇到某中狀況時候結束本次循環執行下次循環或結束這個循環,這就涉及到兩條語句:continue和break。 continue命令可以使程序忽略其後循環體中的其餘指令,直接進行下次循環,而break命令則馬上結束循環,執行循環體後面的的語句。

#測試continue
I=1
while [ $I -lt 10 ]
do
if [ $I -eq 3 ]
then
continue
fi
if [ $I -eq 7 ]
then
break
fi
echo "$I\c"
done

執行上面程序,結果以下:

12456789

與或結構

使用與/或結構有條件的執行命令

Shell程序中可使用多種不一樣的方法完成相同的功能,例如until和while語句就能夠完成相同的功能,一樣,除了if-then-else結 構可使命令有條件的執行外,$$和||操做符也能完成上述功能。在C語言中這兩個操做符分別表示邏輯與和邏輯或操做。在Bourne Shell中,用&&鏈接兩條命令的含義只有前面一條命令成功執行了,後面的命令纔會執行。

&&操做的形式爲:

command && command

例如語句:

rm $TEMPDIR/* && echo "Files successfully removed"

只有rm命令成功執行之後,纔會執行echo命令。若用if-then語句實現上述功能,形式爲:

if rm $TEMPDIR/*
then
echo "Files successfully removed"
fi
相反,用||鏈接兩條命令的含義爲只有第一條命令執行失敗才執行第二條命令,例如:

rm $TEMPDIR/* || echo "File were not removed"

上面語句的等價形式爲:

if rm $TEMPDIR/*
then
:
else
echo "Files were not removed"
fi
這兩種操做符能夠聯合使用,如在下面的命令行中,只有command1和command2執行成功後,command3纔會執行:

command1 && command2 && command3

下面的命令行表示只有command1成功執行,command2不成功執行時,纔會執行command3。

&&和||操做符能夠簡化命令條件執行的格式,但通常只用於一條命令的條件執行。若是許多命令都使用這兩個操做符,那麼整個程序的可讀性將變的不好,因此在多條命令的條件執行時,最好採用可讀性好的if語句。

函數
如今咱們介紹Shell程序中的函數部分,基本上任何高級語言都支持函數這個東西,能讓咱們勝好多事情的東西,至少省的頻繁的敲擊相同的東西,好了come onShell程序中的函數

函數又叫作子程序,能夠在程序中的任何地方被調用,其格式以下:

函數名字()
{
command
... ...
command;
}

Shell程序的任何地方均可以用命令 "函數名字" 調用,使用函數的好處有兩點,一點是使用函數能夠把一個複雜的程序化爲多個模塊,易於管理,符合結構化程序的設計思想,另外一個好處是代碼的重用。

Shell函數和Shel程序比較類似,它們的區別在於Shell程序在子Shell中運行,而Shell函數在當前Shell中運行。所以,在當前Shell中能夠看到Shell函數對變量的修改。在任何Shell中均可以定義函數,包括交互式Shell。

例如:

$dir() {ls -l;}

結果是咱們在$後面打dir,其顯示結果同ls -l的做用是相同的。該dir函數將一直保留到用戶從系統退出,或執行了以下所示的unset命令:
$unset dir
下面的例子說明了函數還能夠接受位置參數:

$dir(){_
>echo "permission ln owner group file sz last access
>ls -l $*;
>}

運行 dir a* 看產生什麼結果

參數a*傳遞到dir函數中而且代替了$*

一般Shell程序將在子Shell中執行,該程序對變量的改變只在子Shell中有效而在當前Shell中無效。"."命令可使Shell程序在當 前Shell中執行。用戶能夠在當前Shell中定義函數和對變量賦值。一般用下面命令來從新初使化.profile對Shell環境的設置。
$ . .profile
因爲看到這部分相對簡單,咱們仍是順便說說trap好了

使用trap命令進行例外處理
用戶編寫程序在程序運行時可能會發生一些例外狀況,好比執行該程序的用戶按中斷鍵或使用kill命令,或者控制終端忽然與系統斷開等。unix系統中的 上述狀況會使系統向進程發一個信號,一般狀況下該信號使進程終止運行。有時侯用戶但願進程在接到終止信號時進行一些特殊的操做。若進程在運行時產生一些臨 時文件,又因接受到的信號而終止。那麼該進程產生的臨時文件將保留下來。在bsh中,用戶可使用trap命令修改進程接收到終止信號時進行的默認操做。
trap命令格式以下:

trap command_string signals

多數系統中共有15種發給進程的信號,默認狀況下大多數信號都會使程序終止。用戶最好查閱本身系統的文擋,看看本系統內使用的信號種類。除了信號爲 9(真正的kill信號)不能使用trap命令外,其餘信號所帶來的操做均可以用trap命令進行指定。下面是trap命令中常用的幾種信號:

信號 功能

1 掛起
2 操做中斷
15 軟終止(kill信號)

若命令串中包含不僅一條命令,必須使用引號將整個命令括起來,具體是單引號仍是雙引號,由用戶是否須要變量替換決定。" "替換,' '不替換。

使用下面trap命令可使程序在接收到掛起、中斷或kill信號時,首先把臨時文件刪除,而後退出:

trap "rm $TEMPDIR/* $$;exit" 1 2 15

在上面例子中,當Shell讀取trap命令時,首先對$TEMPDIR和$$進行變量替換,替換以後的命令串將被保存在trap表中,若上例中 trap命令使用單引號時,trap命令執行時候,不進行變量替換,而把命令串 rm $TEMPDIR/* $$;exit 放到trap表中,當檢測到信號時,程序解釋執行trap表中的命令串,此時進行變量替換。前面變量$TEMPDIR和$$的值爲執行 trap指令時候的值,後一種狀況中變量的值爲程序接收到信號時候的值,因此 "、'必定要區分仔細。

下面命令的含義爲用戶按二次中斷鍵後,程序才終止:

trap 'trap 2' 2

通常trap命令中的命令串中幾乎都包含exit語句,上面rm的例子若無exit語句,接收到信號rm命令執行完後程序將掛起。但有時用戶也須要程序在接到信號後掛起,例如當終端和系統斷開後,用戶發出掛起信號,並執行空命令,以下:

trap : 1

若用戶想取消前trap指令設置的命令串,能夠再執行trap命令,在命令中不指定命令串表示接收到信號後進行默認的操做,命令以下:
trap 1
規範Shell

獲取UNIX類型的選項:

unix有一個優勢就是標準UNIX命令在執行時都具備相同的命令行格式:

command -options parameters

若是在執行Shell程序也採用上述格式,Bourne Shell中提供了一條獲取和處理命令行選項的語句,即getopts語句。該語句的格式爲:

getopts option_string variable

其中option_string中包含一個有效的單字符選項。若getopts命令在命令行中發現了連字符,那麼它將用連字符後面的字符同 option_string相比較。如有匹配,則把變量variable的值設爲該選項。若無匹配,則variable設爲?。當getopts發現連字 符後面沒有字符,會返回一個非零的狀態值。Shell程序中能夠利用getopts的返回值創建一個循環。

下面代碼說明了date命令中怎麼使用getopts命令處理各類選項,該程序除了完成unix的標準命令date的功能外,還增長了許多新的選項。
#新date程序
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTJjwahr OPTION
do
case $OPTION
in
m)date '+%m';;
d)date '+%d';;
y)date '+%y';;
D)date '+%D';;
H0date '+%H';;
M)date '+%M';;
S)date '+%S';;
T)date '+%T';;
j)date '+%j';;
J)date '+%y%j';;
w)date '+%w';;
a)date '+%a';;
h)date '+%h';;
r)date '+%r';;
\?)echo "無效的選項!$OPTION";;
esac
done
fi

有時侯選項中還帶一個值,getopts命令一樣也支持這一功能。這時須要在option_string中選項字母后加一個冒號。當getopts命令 發現冒號後,會從命令行該選項後讀取該值。若該值存在,那麼將被存在一個特殊的變量OPTARG中。若是該值不存在,getopts命令將在OPTARG 中存放一個問號,而且在標準錯誤輸出上顯示一條消息。

下面的例子,實現拷貝一個文件,並給文件賦一個新的名字。-c選項指定程序拷貝的次數,-v選項要求顯示新建立文件的文件名。

#--拷貝程序

COPIES=1
VERBOSE=N
while getopts vc:OPTION
do
case $OPTION
in
c)COPIES=$OPTARG;;
v)VERBOSE=Y;;
\?)echo "無效參數!"
exit 1;;
esac
done
if [ $OPTIND -gt $# ]
then
echo "No file name specified"
exit 2
fi
shift 'expr $OPTIND - 1'
FILE=$1
COPY=0
while [ $COPIES -gt $COPY ]
do
COPY='expr $COPY + 1'
cp $FILE $ {FILE} $ {COPY}
if [ VERBOSE = Y }
then
echo ${FILE} $ {COPY}
fi
done

規範Shell:

咱們知道環境變量PS1是提示符,看下面程序chdir:
if [ ! -d "$!" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1="'pwd'>"
export PS1

咱們執行:

$chdir /usr/ice666

結果提示符號變成/usr/ice666>了嗎?沒有,爲何?

緣由在於:chdir在子Shell中執行,變量PS1的修改在當前Shell中也不會起做用,若要chdir完成意想中的功能,必須在當前Shell 中執行該命令。最好的方法就是把其改爲一個函數而且在.profile文件中定義。但若要把函數放到單個文件中並在當前Shell中執行,則須要使用 . 命令,並將chdir重寫成一個函數,把其中的exit改寫成return。下面代碼是 .ice_ps的內容:

#--提示符
chdir()
{
if [ !-d "$1" ]
then
echo " $1 is not a directory"
return
fi
cd $1
PS1="'pwd'>"
export PS1;
}

而後咱們在.profile文件中加入下面語句

.ice_ps

而後在切換目錄的時候,咱們用chdir命令,結果是什麼呢,本身實驗好了!
調試Shell程序

1>調試shell程序

用戶剛編寫完Shell程序中,不可避免的會有錯誤,這時咱們能夠利用Bsh中提供的跟蹤選項,該選項會顯示剛剛執行的命令及參數。用戶能夠經過set命令打開-x選項或在啓動Shell使用-x選項將Shell設置成跟蹤模式。例若有下面代碼ice_tx:

if [ $# -eq 0 ]
then
echo "usage:sumints integer list"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum='expr $sum + $1'
shift
done
echo $sum

咱們用跟蹤模式運行:

$sh -x ice_tx 2 3 4
結果顯示:
+[ 3 -eq 0 ]
+sum=0
+[ 3 -eq 0 ]
+expr 0+2
+sum=2
+shift
+[ 2 -eq 0 ]
+expr 2+3
+sum=5
+shift
+[ 1 -eq 0 ]
+expr 5+4
+sum=9
+[ 0 -eq 0 ]
+echo 9
9

從上面能夠看出,跟蹤模式下Shell顯示執行的每一條命令以及該命令使用的變量替換後的參數值。一些控制字如if、then、until等沒顯示。

2>命令分組

Shell中若干命令能夠組成一個單元一塊兒執行。爲了標識一組命令,這些命令必須放到"()"或"{}"中。放在"()"中的命令將在子Shell中運 行,而放在"{}"中的命令將在當前Shell中運行。子Shell中運行的命令不影響當前Shell的變量。當前Shell中運行的命令影響當前 Shell的變量。

$NUMBER=2
$(A=2;B=2;NUMBER='expr $A+$B';echo $NUMBER)
結果爲:4
$echo $NUMBER
結果爲:2
若是把上面的()變成{},結果會是怎麼樣的呢?

3>使用Shell分層管理器shl

UNIX是一個多道程序設計的操做系統,一些UNIX系統利用這一特性提供了Shell層次管理器shl。使用shl用戶一次能夠打開多個層次的Shell,其中活躍的Shell能夠從終端上得到輸入。但全部Shell的輸出均可在終端上顯示,除非顯示被禁止。

多個Shell中有一個爲shl,當用戶在某個Shell中工做時,能夠經過使用特殊字符(通常爲Ctrl+z)返回shl。爲了同其餘Shell區 別,shl中提示符爲">>>"。當用戶工做在Shell層次管理器中時,能夠建立、激活和刪除Shell,下面是shl中使用的命 令。

create name 產生名爲name的層次
delete name 刪除名爲name的層次
block name 禁止名爲name的層次的輸出
unblock name 恢復名爲name的層次的輸出
resume name 激活名爲name的層次
toggle 激活近來常用的層次
name 激活名爲name的層次

layers [-l] name 對於表中的每一個層次,顯示其正在運行的進程的進程號,-l選項要求顯示詳細信息。

help 顯示shl命令的幫助信息
quit 退出shl以及全部被激活的層次

總結
在前面咱們主要介紹了sh的變量、基本語法、程序設計等。若是掌握了這些內容,在學習其餘UNIX下編程語言的時候,相信有必定的好處,咱們說了,在大 多數的UNIX中都提供Bourn Shell,並且不多有象sh這樣強大的腳本編輯語言了,是系統管理員和程序員的一筆財富,而且不須要額外的軟件環境,對文件等處理藉助unix命令,實 現起來比c實現還要簡單。

  原文地址 http://cublog.cn/u/11557/?u=http://cublog.cn/u/11557/showart.php?id=70342
相關文章
相關標籤/搜索