對話 UNIX: 更多 shell 腳本技術

盡管在近兩年使用過 Unix 的一些人可能嘗試過 shell 腳本編程,但是他們很可能只是研究操作系統的細節,并不精通 shell 腳本編程 。本文針對那些希望進一步了解 shell 腳本,并開始編寫更高級腳本的讀者 。本文提供腳本編程的基礎知識,包括如何簡化腳本、如何盡可能保持腳本的靈活性、如何編寫干凈的腳本、在腳本內編寫注釋以及調試腳本 。
保持簡單
在人們學習如何編寫 shell 腳本時,常常遇到的一個問題是,重復他們在另一個腳本中已經做過的工作 。他們其實不需要復制原來的腳本并修改幾個硬編碼值,只需創建一個函數來處理兩個腳本的重復部分 。創建集中的函數還可以促進標準化,幫助創建統一的腳本 。如果一個函數在腳本的一個部分工作正常,那么它在腳本中的其他地方也會正常工作 。
例如,清單 1 所示的腳本應該濃縮和簡化為更簡單、更干凈的程序 。
清單 1. 可以簡化的腳本示例
#!/usr/bin/ksh
if [[ $# -lt 2 ]]
then
 echo "Usage: ${0##*/}
 exit 0
fi
if [[ ! -f "${1}" ]]
then
 echo "Unable to find file '${1}'"
 exit 1
fi
if [[ ! -r "${1}" ]]
then
 echo "Unable to read file '${1}'"
 exit 2
fi
gzip ${1}
ls -l ${1}.gz
if [[ ! -f "${2}" ]]
then
 echo "Unable to find file '${2}'"
 exit 1
fi
if [[ ! -r "${2}" ]]
then
 echo "Unable to read file '${2}'"
 exit 2
fi
gzip ${2}
ls -l ${2}.gz
這個腳本看起來很糟糕?。ㄖx天謝地,它只是一個示例) 。這個腳本應該盡可能進行濃縮 。從便于閱讀的角度來看,清單 2 提供的版本更干凈 。
清單 2. 對清單 1 腳本進行濃縮的版本
#!/usr/bin/ksh
exit_msg() {
 [[ $# -gt 1 ]] && echo "${0##*/} (${1}) - ${2}"
 exit ${1:-0}
}
[[ $# -lt 2 ]] && exit_msg 0 "Usage: ${0##*/}
for _FNAME in $@
do
 [[ ! -f "${_FNAME}" ]] && exit_msg 1 "Unable to find file '${_FNAME}'"
 [[ ! -r "${_FNAME}" ]] && exit_msg 2 "Unable to read file '${_FNAME}'"
 gzip ${_FNAME}
 ls -l ${_FNAME}.gz
done
注意到這兩者的差異了嗎?這個腳本增加了一個簡單的函數來顯示一個消息并帶適當的返回碼退出,還把所有操作轉移到一個 for 循環中,這使這個腳本看起來更干凈、更容易理解了 。
保持靈活性
編程和 shell 腳本編程的新手常常犯的另一個錯誤是,在程序或 shell 腳本中對靜態值進行硬編碼 。這會限制腳本的靈活性,是一種糟糕的編程習慣 。這迫使管理員或開發人員不得不經常修改腳本以使用其他值;為了避免這個問題,應該使用變量并為腳本或函數提供參數 。
例如,清單 3 是一個編寫得很差的不靈活的示例腳本 。
【對話 UNIX: 更多 shell 腳本技術】清單 3. 不靈活的示例腳本
#!/bin/bash
if [[ -f /home/cormany/FileA ]]
then
 echo "Found file '/home/cormany/FileA'"
elif [[ -f /home/cormany/DirA/FileA ]]
then
 echo "Found file '/home/cormany/DirA/FileA'"
else
 echo "Unable to find file FileA"
fi
這個腳本可以正常工作,但是它只能在兩個位置搜索一個文件 。
清單 4 提供相同的功能,但是允許用戶在任何位置搜索任何文件 。
清單 4. 使腳本更靈活
#!/bin/bash
exit_msg() {
 [[ $# -gt 1 ]] && echo "${0##*/} (${1}) - ${2}"
 exit ${1:-0}
}
[[ $# -lt 2 ]] && exit_msg 1 "Usage: ${0##*/} "
_FNAME="${1}"
_DNAME="${2}"
[[ ! -d "${_DNAME}" ]] && exit_msg 2 "Unable to read or find Directory '${_DNAME}'"

推薦閱讀