使用bash编写Linux shell脚本--复合命令
來源:http://blog.csdn.net/fox_lht/article/details/5897336
除了最簡單的腳本,你很少想要執行每一個命令。執行一組命令或者重復執行一組命令若干次比執行單個命令更加有助。復合命令是將命令封裝在一組其他命令中。
從可讀性來說,封裝后的命令使用縮進格式將會使復合命令的代碼清晰并便于閱讀。管理員曾經抱怨過我的縮進比標準的縮進少了一個空格(我必須使用尺子在屏幕上測量才能確定此事),我認為這不是什么問題,但是他說,當輸入 0 時,它的程序會崩潰。
復合命令總是有兩個命令組成。命令的結束符是該命令相反拼寫順序,就像使用括號將命令括住了。例如:神秘莫測的命令 esac 實際上是復合命令 case 的結束符。
命令狀態碼
每一個 Linux 命令都返回一個狀態碼(退出狀態),他是一個 0~255 之間的數字,用來表示該命令遇到的問題。如果狀態碼返回的是 0 ,則表示該命令運行成功,其他的狀態碼表示某種錯誤。
狀態碼包含在變量“ $? ”中。
$ unzip no_file.zip
unzip:? cannot find no_file.zip, no_file.zip.zip or no_file.zip.ZIP.
$ printf “%d/n” “$?”
9
unzip 命令找不到要解壓的文件,返回的狀態碼是 9 。
非官方的 Linux 慣例使用狀態碼 127 并且比標準的錯誤代碼要小。例如: ls 返回了狀態碼 9 ,它表示“ bad file number ”。完整的錯誤代碼列在附錄 D :“錯誤代碼”中。
如果命令被信號中斷, Bash 返回狀態碼 128 ,加上信號碼。最終,用戶的錯誤碼應該大于 191 , Bash 返回的錯誤碼為 63 。信號碼列在附錄 E :信號。
if test ! -x “$who” ; then
printf “$SCRIPT:$LINENO: the command $who is not available – “/
“ aborting/n “ >&2
exit 192
fi
一般,大部分 Linux 命令只是簡單的返回 1 或 0 ,表示失敗還是成功。這也許就是你的腳本所需要的所有信息。特殊的錯誤信息任然顯示在標準輸出上。
$ ls po_1473.txt
po_1473.txt
$ printf “%d/n” $?
0
$ ls no_file
no_file not found
$ printf “%d/n” $?
1
狀態碼不同于 let 命令返回的真值(第六章討論過),本節稱之為邏輯表達式。在 let 命令中, false 的值是 0 ,這符合計算機語言的習慣,但是狀態碼是 0 表示成功而不是失敗。
$ let “RESULT=1>0”
$ printf “%d %d/n” “$RESULT” $?
1 0
$ test 1 -gt 0
$ printf “%d/n” $?
0
let 命令分配 1 給 RESULT ,表明 1 大于 0 。 test 命令返回狀態碼 0 表明命令運行成功。 let 命令返回狀態碼 0 ,表明 let 命令成功進行比較。
這些相反的碼和習慣可能會導致錯誤,這些錯誤很難調試出來。 Bash 有兩個內置命令 true 和 false 。這些是返回的狀態碼,而不是 let 命令的真值。
$ true
$ printf “%d/n” “$?”
0
$ false
$ printf “%d/n” “$?”
1
true 命令分配一個成功的狀態碼( 0 )。 fasle 分配一個錯誤的狀態碼( 1 )。
有點混亂吧?
如果你需要保存邏輯比較的成功狀態最好還是使用 test 命令。大部分外殼使用狀態碼而不是真值。
在管道中,一次運行幾個命令。從管道返回的狀態碼是最后一個命令的狀態碼。下面的示例中,顯示的是 wc 命令而不是 ls 命令的狀態碼。
$ ls badfile.txt | wc -l
ls: badfile.txt: No such file or directory
0
$ printf “%d/n” “$?”
0
雖然 ls 報告了一個錯誤,管道返回的還是成功的狀態碼,因為 wc 命令是運行成功的。
Bash 也定義了一個數組稱之為 PIPESTATUS ,它包含了上此運行管道中每一個命令的單獨狀態。
$ ls badfile.txt | wc -l
ls: badfile.txt: No such file or directory
0
$ printf “%d %d/n” “${PIPESTATUS[0]}” “${PIPESTATUS[1]}”
1 0
$? 是 PIPESTATUS 數組的最后一個值的別名。
一個命令或管道可以被“!”進行對狀態進行取反操作,如果狀態時 0 取反則為 1 , 如果大于 0 ,取反則為 0 。
if 命令
if 命令執行二選一或多選一的操作。
通常 if 命令和 test 命令一起使用。
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$CUTOFF” ] ; then
printf “%s/n” “Too few orders...try running again later”
exit 192
fi
這個例子是對當前目錄中的文件進行統計,如果沒有足夠的文件數,則顯示一則消息,否則就到 fi 命令結束。
then 命令前分號是必須要有的,雖然它是和 if 一起工作的,但是它仍然是一個單獨的命令,所以需要分號進行分割。
if 命令亦可以有一個 else 命令的分支,它可以在條件失敗的時候運行。
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$CUTOFF” ] ; then
printf “%s/n” “Too few orders...but will process them anyway”
else
printf “%s/n” “Starting to process the orders”
fi
if 命令內部可以嵌套 if 命令。
NUM_ORDERS=`ls -1 | wc -l`
if [[ $NUM_ORDERS -lt $TOOFEW ]] ; then
printf “%s/n” “Too few orders...but will process them anyway”
else
if [[ $NUM_ORDERS -gt $TOOMANY ]] ; then
printf “%s/n” “There are many orders.? Processing may take a long time”
else
printf “%s/n” “Starting to process the orders”
fi
fi
if 不可以交叉嵌套,即:里面的 if 必須完全在外部 if 命令內。
為了實現多分支, if 命令可以有 elif 分支, elif 命令是 else if 的簡寫,它可以減少不必要的嵌套。 elif 命令的最后可以在最后加一個 else 命令,他在所有條件都沒有中的時候執行。有了這些知識,你可以重寫上面的示例:
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$TOOFEW” ] ; then
printf “%s/n” “Too few orders...but will process them anyway”
elif [ “$NUM_ORDERS” -gt “$TOOMANY” ] ; then
printf “%s/n” “There are many orders.? Processing may take a long time”
else
printf “%s/n” “Starting to process the orders”
fi
if 命令也可以不和 test 命令一起使用,它可以根據命令返回的狀態碼進行執行相關的任務。
if rm “$TEMPFILE” ; then
printf “%s/n” “$SCRIPT:temp file deleted”
else
printf “%s - status code %d/n” /
“ $SCRIPT:$LINENO: unable to delete temp file” $? 2>&
fi
在 if 命令中嵌入復雜的命令會使腳本語言難讀且難以調試。你應該避免這樣做。在這個例子中,如果 rm 命令運行失敗,則它先顯示自己的提示信息,接著顯示腳本中的信息。盡管在 if 命令內部也可以聲明變量,但是它很難確定那個變量存在,那個不存在。
case 命令
case 命令進行模板匹配測試,如果值和某個模板匹配,則執行相應的命令。變量逐個進行測試。
和 elif 命令不同,測試的狀態碼來自同一個命令, case 測試變量的值。如果測試字符串的值, case 命令比 elif 命令更好。
每一個 case 分支都必須用一對分號(;;)進行分割。如果沒有分號, Bash 會執行下一個分支并報錯。
printf “%s -> “ “1 = delete, 2 = archive.? Please choose one”
read REPLY
case “$REPLY” in
1) rm “$TEMPFILE” ;;
2) mv “$TEMPFILE” “$TEMPFILE.old” ;;
*) printf “%s/n” “$REPLY was not one of the choices” ;;
esac
星號表示所有沒有匹配模板的條件所執行的任務。雖然這是可選的,但是好的設計應該有一個這樣的寫法,即使里面是一個空語句(:)也是好的。
模板匹配規則遵循 globbing 規則,參考前一張杰的內容。例如:豎條可以分開多個模板。
case 同其他計算機語言不一樣,不會跟著執行。當一個選擇了一個條件,則其他 case 不會執行。
while 循環
有幾個命令都可以實現重復執行一組命令。
while 命令根據測試條件執行封閉在 while 命令中命令組。如果命令失敗,則在 while 命令中的命令組不執行。
printf “%s/n” “Enter the names of companies or type control-d”
while read -p “Company ?” COMPANY; do
if test -f “orders_$COMPANY.txt” ; then
printf “%s/n” “There is an order file from this company”
else
printf “%s/n” “There are no order files from this company”
fi
done
while 命令使用 done 命令結束。不是你也許認為的 elihw 這樣的命令。
使用 true 命令作為測試條件, while 命令會無限循環下去,因為 true 總是返回成功,循環無疑會一直下去。
printf “%s/n” “Enter the names of companies or type quit”
while true ; do
read -p “Company ?” COMPANY
if [ “$COMPANY” = “quit” ] ; then
break
elif test -f “orders_$COMPANY.txt” ; then
printf “%s/n” “There is an order file from this company”
else
printf “%s/n” “There are no order files from this company”
fi
done
一個 while 循環可以使用 break 命令提前停止。在到達 break 命令后, Bash 會跳出循環并執行循環外的第一條命令。
break 后面可以跟著一個數字,表示跳出幾層循環。例如:
break 2
跳出 2 層循環。
和 break 對應的是 continue 命令,它會對后面的命令忽略,從頭開始從新循環。 continue 命令后面也可以跟一個數字表示跳到哪一層的循環。
until 循環
和 while 循環對應的是 until 循環命令, until 循環是直到測試條件成功才停止執行封閉在 until 語句中命令組,其他基本上和 until 命令相同。它相當于 while !。
until test -f “$INVOICE_FILE” ; do
printf “%s/n” “Waiting for the invoice file to arrive...”
sleep 30
done
將 false 和 until 一起使用可以建立無限循環, break 和 continue 命令同樣也可以用于 until 循環命令。
for 循環命令
標準的伯恩 for in loop 是變量在這兒文件。 for 命令將一系列值分別放入變量中然后執行包含的命令。
for FILE_PREFIX in order invoice purchase_order; do
if test -f “$FILE_PREFIX””_vendor1.txt” ; then
printf “%s/n” “There is a $FILE_PREFIX file from vendor 1...”
fi
done
如果 in 后面的參數沒有,則 for 在外殼腳本中參數中進行循環。
break 和 continue 命令可以用于 for 循環。
因為其他外殼的特性, for 循環不是通用的。
嵌入 let 命令((( .. )))
let 命令判斷如果表達式是 0 則返回狀態碼 1 ,如果表達式不為 0 ,則返回 0 。和 test 命令可以使用一對方括號來表示更容易閱讀一樣, let 命令也有更容易閱讀的表示,使用雙括號。
下面的列表 7.1 示例使用了 for 循環嵌入 let 命令的表達方式:
列表 7.1
#!/bin/bash
# forloop.sh: Count from 1 to 9
for (( COUNTER=1; COUNTER<10; COUNTER++ )) ; do
printf “The counter is now %d/n” “$COUNTER”
done
exit 0
當循環開始時,執行雙括號中的第一個表達式,每次循環開始執行第三個表達式,并檢查第二個表達式,當第二個表達式返回 false ,循環結束。
$ bash forloop.sh
The counter is now 1
The counter is now 2
The counter is now 3
The counter is now 4
The counter is now 5
The counter is now 6
The counter is now 7
The counter is now 8
The counter is now 9
命令組( {..} )
命令可以使用大括號組合到一個組內。
ls -1 | {
while read FILE ; do
echo “$FILE”
done
}
在本實例中, ls 命令的結果成為組命令的輸入。
$ test -f orders.txt && { ls -l orders.txt ; rm orders.txt; } /
|| printf “no such file”
如果文件 orders.txt 存在,文件顯示出來,接著被刪除。否則顯示“ no such file ”。在大括號中的命令需要分號進行分割。
命令也可以使用子外殼進行分組,子外殼將在第九章進行討論。
report.bash :報表格式化
report.bash 是一個用來給銷售數字建立報表的腳本程序。銷售數字文件有產品名稱、本國銷售數、外國銷售數來組成。例如: report.bash 把下面的報表
binders 1024 576
pencils 472? 235
rules 311? 797
stencils 846 621
轉換為
Report created on Thu Aug 22 18:27:07 EDT 2002 by kburtch
Sales Report
Product????????? Country???? Foreign?????? Total???? Average
—— ??????????????? ——??????? ——??????? ——??????? ——
binders???????????? 1024???????? 576??????? 1600???????? 800
pencils????????????? 472???????? 235???????? 707???????? 353
rules??????????????? 311???????? 797??????? 1108???????? 554
stencils???????????? 846???????? 621??????? 1467???????? 733
—— ??????????????? ——??????? ——??????? ——??????? ——
Total number of products: 4
End of report
列表 7.2 report.bash
!/bin/bash
#
# report.bash: simple report formatter
#
# Ken O. Burtch
# CVS: $Header$
# The report is read from DATA_FILE.? It should contain
# the following columns:
#
#?? Column 1: PRODUCT = Product name
#?? Column 2: CSALES? = Country Sales
#?? Column 3: FSALES? = Foreign Sales
#
# The script will format the data into columns, adding total and
# average sales per item as well as a item count at the end of the
# report.
# Some Linux systems use USER instead of LOGNAME
if [ -z “$LOGNAME” ] ; then?????????? # No login name?
declare –rx LOGNAME=”$USER”??????? # probably in USER
fi
shopt -s -o nounset
# Global Declarations
declare -rx SCRIPT=${0##*/}?????????? # SCRIPT is the name of this script
declare -rx DATA_FILE=”report.txt”??? # this is raw data for the report
declare -i? ITEMS=0?????????????????? # number of report items
declare -i? LINE_TOTAL=0????????????? # line totals
declare -i? LINE_AVG=0??????????????? # line average
declare???? PRODUCT?????????????????? # product name from data file
declare -i? CSALES??????????????????? # country sales from data file
declare -i? FSALES?????????????? ????? # foreign sales from data file
declare -rx REPORT_NAME=”Sales Report” # report title
# Sanity Checks
if test ! -r “$DATA_FILE” ; then
printf “$SCRIPT: the report file is missing—aborting/n” >&2
exit 192
fi
# Generate the report
printf “Report created on %s by %s/n” “`date`” “$LOGNAME”
printf “/n”
printf “%s/n” “$REPORT_NAME”
printf “/n”
printf “%-12s%12s%12s%12s%12s/n” “Product” “Country” “Foreign” “Total” “Average”
printf “%-12s%12s%12s%12s%12s/n” “——” “——” “——” “——” “——”
{ while read PRODUCT CSALES FSALES ; do
let “ITEMS+=1”
LINE_TOTAL=”CSALES+FSALES”
LINE_AVG=”(CSALES+FSALES)/2”
printf “%-12s%12d%12d%12d%12d/n” “$PRODUCT” “$CSALES” “$FSALES” /
“ $LINE_TOTAL” “$LINE_AVG”
done } < $DATA_FILE
# Print report trailer
printf “%-12s%12s%12s%12s%12s/n” “——” “——” “——” “——” “——”
printf “Total number of products: %d/n” “$ITEMS”
printf “/n”
printf “End of report/n”
exit 0
總結
以上是生活随笔為你收集整理的使用bash编写Linux shell脚本--复合命令的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos上安装adobe flash
- 下一篇: AgileEAS.NET SOA 中间件