為了成為一個靈活的工具,一個合格的指令碼必須提供額外的資訊來說明此指令碼的作用,如何執行此指令碼以及在哪兒執行此指令碼。和命令一樣指令碼也使用參數。開關和參數提高了重用性同時也減少了成本,節省了時間。
定位的參數
有三種有效方法可以使Linux指令碼使用參數。第一種使用定位參數。指令碼根據在命令列出現參數的位置調用參數。因為其他兩種依賴於定位參數,所以先討論這個。
Bash變數使用“$0”標示指令碼的路徑。不必是全路徑名,但是它定義了執行指令碼所在的路徑。
$ printf “%s\n” “$0”
/bin/bash
在這個例子中,Bash會和開始命令/bin/bash。
當參數命令組合了basename命令時,只留下指令碼的名字,其餘的路徑部分被刪除了。
一些微縮版本使用Bash的字串替換功能來避免執行外面的程式。
$ declare -rxSCRIPT=${0##*/}
$ printf “%s\n” “$SCRIPT”
Bash
通過使用“$0”來找到指令碼的名字,在指令碼被拷貝和重新命名之後,就不會出現錯誤檔案名稱的潛在威脅了。SCRIPT總是保持這正確的指令碼名。
變數“$#”包含有指令碼或外殼會話參數的個數。如果沒有參數,$#總是0。這個參數沒有將指令碼名包含在$0中。
$ printf “%d\n” $#
0
前面九個參數放置在變數$1~$9中。(九個之後的參數如果要訪問使用大括弧)。如果設定了nounset外殼選項,訪問一個未定義的參數會產生一個錯誤,就像未定義變數名一樣的錯誤。
$ printf “%s\n” $9
bash: $9: unboundvariable
變數“$@”或者是“?*”將所有參數作為一個字串返回。
當使用定位參數時,Bash並不區分它們是參數還是開關,對於指令碼來說在命令列的每一個項目作為獨立的參數來對待。
考慮一下下面的指令碼,顯示在列表9.1中:
Listing 9.1 params.sh
#!/bin/bash
#
# params.sh: apositional parameter demonstration
printf “There are %dparameter(s)\n” “$#”
printf “The completelist is %s\n” “$@”
printf “The firstparameter is %s\n” “$1”
printf “The secondparameter is %s\n” “$2”
當運行此指令碼並帶上參數“-c”和“t2341”,它表示“$1”是“-c”,“$2”是“t2341”。
$ bash parms.sh -c t2341
There are 2 parameter(s)
The complete list is -ct2341
The first parameter is-c
The second parameter ist2341
雖然“$@”和“$*”都表示所有的參數,但是如果他們用雙引號封裝起來的含義是有所不同的。“$@”根據IFS變數的第一個字元進行分割,如果IFS為空白則使用空格,如果IFS沒有定義,則不使用任何東西。“$*”將一組參數作為一個單獨的組。
“$@”總是使用空格進行分割,並將參數視為一個個單獨的項目,即使它們使用雙引號包起來也是這樣。“$@”通常用於將整個開關集合傳輸給另一個命令(例如:ls $@)。
雖然定位參數是一個簡單的方法來遍曆開關和參數,它們並不是總是這樣直接遍曆參數列表的,有一個內建命令shift,它可以將參數“$1”給丟棄掉,將後面的參數前移一位。使用shift命令,你可以檢查每一個參數,就像它們總是第一個參數一樣。
列表9.2展示了如何使用shift的完整例子:
Listing 9.2 param2.sh
#!/bin/bash
#
# param2.sh
#
# This script expectsthe switch -c and a company name. --help (-h)
# is also allowed.
shopt -s -o nounset
declare -rxSCRIPT=${0##*/}
# Make sure there is atleast one parameter or accessing $1
# later will be anerror.
if [ $# -eq 0 ] ; then
printf “%s\n” “Type--help for help.”
exit 192
fi
# Process the parameters
while [ $# -gt 0 ] ; do
case “$1” in
-h | --help) # Show help
printf “%s\n” “usage:$SCRIPT [-h][--help] -c companyid”
exit 0
;;
-c ) shift
if [ $# -eq 0 ] ; then
printf “$SCRIPT:$LINENO:%s\n” “company for -c is missing” >&2
exit 192
fi
COMPANY=”$1”
;;
-* ) printf“$SCRIPT:$LINENO: %s\n” “switch $1 not supported” >&2
exit 192
;;
* ) printf“$SCRIPT:$LINENO: %s\n” “extra argument or missing switch” >&2
exit 192
;;
esac
shift
done
if [ -z “$COMPANY” ] ;then
printf “%s\n” “companyname missing” >&2
exit 192
fi
# <-- begin work here
exit 0
最後一個有關的參數是“$_”(貨幣符號加上底線)。這個開關有兩個作用,首先當外殼指令碼首先開始時,它表示為外殼或外殼指令碼的路徑名,其次,在每個命令執行之後,當前命令被放置在環境變數中。
$ /bin/date
Fri Jun 29 14:39:58 EDT2001
$ printf “%s\n” “$_”
/bin/date
$ date
Fri Jun 29 14:40:04 EDT2001
$ printf “%s\n” “$_”
date
你可以使用“$_”來重複上一次的參數。
getopts命令
使用定位參數有兩個限制,首先,他需要編程者自己測試錯誤並建立相應的訊息。其次,shift命令會刪除掉所有的參數,如果你想在以後再次訪問他們,將是不可能的。
為了處理這些問題。Bash包含了一個內建命令getopts,它可以提取並檢查開關而不會弄亂定位參數。意外出現的參數或缺少的參數會重新識別並報告錯誤。
使用getopts需要坐一些準備工作,首先,你必須定於一個想要使用開關的字串。通常這個變數稱之為OPTSTRING。如果開關需要一個參數,在該開關後加一個冒號。
例如param2.sh需要-h和-c加上公司標識的參數,OPTSTRING是“hc:”。
在選項列表後面還需要第二個參數,該參數儲存外殼命令當前使用的參數。
每次getopts運行,命令列的第二個開關將會被檢查是否包含在參數列表中,並將名字儲存在變數SWITCH中。下一個要檢查的參數的位置稱之為 OPTING。如果它不存在,OPTING在第一個指令碼參數檢查之前自動化佈建為1。如果有參數,他被儲存在變數OPTARG中。列表9.3展示一個腳步, 它會測試指令碼的第一個參數。
Listing 9.3 getopts.sh
#!/bin/bash
#
# getopts.sh
declare SWITCH
getopts “hc:” SWITCH
printf “The first switchis SWITCH=%s OPTARG=%s OPTIND=%s\n” \
“$SWITCH” “$OPTARG”“$OPTIND”
在這個指令碼中,未知的開關被分配一個問號給SWITCH變數,並顯示一條錯誤資訊。
$ bash getopts.sh -h
The first switch isSWITCH=h OPTARG= OPTIND=2
$ bash getopts.sh -c a4327
The first switch isSWITCH=c OPTARG=a4327 OPTIND=3
$ bash gettopts.sh -a
t.sh: illegal option --a
The first switch isSWITCH=? OPTARG= OPTIND=1
錯誤資訊可以在開關列表的第一字元前加一個冒號進行隱藏,通過使用“:hc:”,使用錯誤開關-a時就不會顯示錯誤了,但是該錯誤開關會被儲存在OPTARG中,以便自訂錯誤資訊用。
$ bash getopts.sh -a
The first switch isSWITCH=? OPTARG=a OPTIND=1
你也可以通過建立OPTERR變數並賦值為0來隱藏錯誤訊息。它將被合法的開關字串所覆蓋掉。
開關通常使用while和case語句進行檢查,請看列表9.4:
Listing 9.4 getopts_demo.sh
# getopts_demo.sh
#
# This script expectsthe switch -c and a company name. --help (-h)
# is also allowed.
shopt -s -o nounset
declare -rxSCRIPT=${0##*/}
declare -rOPTSTRING=”hc:”
declare SWITCH
declare COMPANY
# Make sure there is atleast one parameter
if [ $# -eq 0 ] ; then
printf “%s\n” “Type--help for help.”
exit 192
fi
# Examine individualoptions
while getopts“$OPTSTRING” SWITCH ; do
case $SWITCH in
h) printf “%s\n” “usage:$SCRIPT [-h] -c companyid”
exit 0
;;
c) COMPANY=”$OPTARG”
;;
\?) exit 192
;;
*) printf“$SCRIPT:$LINENO: %s\n” “script error: unhandled argument”
exit 192
;;
esac
done
printf “$SCRIPT: %s\n”“Processing files for $COMPANY...”
This script is shorterthan the positional
這個指令碼比定位參數的指令碼更短,如果getopts出錯,switch語句會不運行。
作為一個特定的情況,如果提供getopts命令作為一個額外的參數,getopts能夠處理這些變數而不是指令碼參數,這樣可以使用特定的參數來測試開關。
getopt命令
雖然getopts命令使得指令碼的編程稍微容易點,但是它沒有遵循Linux開關標準,特別是getopts不允許使用雙減號長開關。
為了繞開這個限制,Linux包含了它自己的getopt命令(注意不是前面的getopts)。同getopts的作用類似,但是getopt可以使用長開關並具有一些getopts沒有的特性。它在指令碼中以一種完全不同的方法使用。
因為getopt是一個外部命令,它不能像想getopts那樣將開關儲存在變數中。它沒有辦法將環境變數輸出回給指令碼。
同樣,getopt不知道外殼指令碼有哪些開關,除非使用“$@”命令將開關複製給getopt命令。最終,getopt不是使用迴圈,而是將所有的參數作為單獨的一個組進行一次性處理。
如同getopts,getopt使用OPTSTRING的列表選項,這個列表可以使--options(-o)引導,以便使系統清楚後面是開關的列表,開關可以使用逗號進行分割。
傳遞給指令碼的選項表必須使用雙減號和“$@”追加給getopt命令。雙減號表明getopt開關結束的地方和指令碼開始的地方。
列表9.5展示的指令碼是使用getopt命令完成getopts.sh一樣的功能。注意--name(或者-n)開關用於將指令碼的名字傳遞給getopt命令用在任何錯誤的訊息中。
Listing 9.5 getopt.sh
#!/bin/bash
#
#getopt.sh – ademonstration of getopt
declare -rxSCRIPT=${0##*/}
declare RESULT
RESULT=’getopt --name“$SCRIPT” --options “-h, -c:” -- “$@”’
printf “status code=$?result=\”$RESULT\”\n”
下面是運行程式的結果:
$ bash getopt.sh -h
status code=0 result=”-h --”
$ bash getopt.sh -c
getopt.sh: optionrequires an argument -- c
status code=1 result=”--”
$ bash getopt.sh -x
getopt.sh: invalidoption -- x
status code=1 result=”--”
狀態代碼(status code)表明運行結果是否成功。狀態代碼為1,表示getopt顯示錯誤資訊。狀態代碼為2表示給getopt命令的選項有問題。
長開關使用--longoptions(或者-l)。它包含逗號分隔的長選項列表。例如:允許使用--help則使用下面的文法:
RESULT=’getopt--name “$SCRIPT” --options “-h, -c:” --longoptions “help” -- “$@”’
getopt還有一個增強。為了給一個長選項指定一個選項參數,增加一個等號和參數名。
如果雙冒號跟著開關名,它表明該開關是一個可選的參數而不是必需使用的。如果POSIXLY_COMPATIBLE變數存在,選項表以“+”開始。開關不允許使用參數且第一個參數作為開關項目的結束。
如果GETOPT_COMPATIBLE外殼變數存在,getopt的行為更新C語言標準庫中的getopt。一些老版本中的getopt將這種行 為作為預設值。如果你需要檢查這種行為,使用--test(或者-T)開關來測試它的C語言相容模式:如果不是在相容模式,狀態代碼返回4。
在getopt命令檢查完開關後要做什麼呢?它們使用set命令來替換原始參數。
evalset – “$RESULT”
現在參數可以使用定位參數檢查也可以使用內建的getopts檢查,如列表9.6所示:
Listing 9.6 getopt_demo.sh
#!/bin/bash
#
# getopt_demo.sh
#
# This script expects the switch -c and a companyname. --help (-h)
# is also allowed.
shopt -s -o nounset
declare -rx SCRIPT=${0##*/}
declare -r OPTSTRING=”-h,-c:”
declare COMPANY
declare RESULT
# Check getopt mode
getopt -T
if [ $? -ne 4 ] ; then
printf “$SCRIPT: %s\n” “getopt is in compatibilitymode” >&2
exit 192
fi
# Test parameters
RESULT=’getopt --name “$SCRIPT” --options “$OPTSTRING”\
--longoptions “help” \ -- “$@”’
if [ $? -gt 0 ] ; then
exit 192
fi
# Replace the parameters with the results of getopt
eval set -- “$RESULT”
# Process the parameters
while [ $# -gt 0 ] ; do
case “$1”in
-h | --help) # Show help
printf “%s\n” “usage: $SCRIPT [-h][--help] -ccompanyid”
exit 0
;;
-c ) shift
if [ $# -eq 0 ] ; then
printf “$SCRIPT:$LINENO: %s\n” “company for -c ismissing” >&2
exit 192
fi
COMPANY=”$1”
;;
esac
shift
done
if [ -z “$COMPANY” ] ; then
printf “%s\n” “company name missing” >&2
exit 192
fi
printf “$SCRIPT: %s\n” “Processing files for$COMPANY...”
# <-- begin work here
exit 0
看上去好像多做了許多工作,但是當指令碼有許多複雜的開關時,getopt使得處理參數變得更容易些。
還有一些特殊的開關,--alternative(或者-a)開關允許長選項使用一個單獨的減號作為前置字元。使用這個開關違背了Linux協議約 定。--quiet-output(或者-Q)可以在檢查完後不返回已處理列表給標準輸出裝置。--quiet(或者-q)表明只返回狀態代碼不返回任何錯 誤資訊,以便你定義自己的錯誤資訊。--shell開關使用引號來保護特定字元。例如空格等。它也許是外殼處理這些字元的一種特殊的方法(只有在C語言兼 容模式才有用)。
子外殼(subshell)
第七章中“複合命令”提到的一組命令可以使用大括弧組合在一起。這些命令就像被分配給了一個組,而且只返回一個狀態代碼。
$ { sleep 5 ; printf “%s\n” “Slept for 5 seconds” ;}
休眠5秒。
子外殼是使用小括弧包含起來的一組命令。和命令組不同,如果子外殼單獨佔用一行,最後一個命令不需要使用分號。
$ ( sleep 5 ; printf “%s\n” “Slept for 5 seconds” )
休眠5秒。
子外殼就像使用括弧括起來的命令組和獨立指令碼的混合體。象命令組一樣它返回單獨的狀態代碼,象獨立的外殼指令碼,它有自己的環境變數。
$ declare -ix COUNT=15
$ { COUNT=10 ; printf “%d\n” “$COUNT” ; }
10
$ printf “%d\n” “$COUNT”
10
$ ( COUNT=20 ; printf “%d\n” “$COUNT” )
20
$ printf “%d\n” “$COUNT”
10
在這個樣本中,命令組可以改變變數COUNT的值,而在子外殼中,沒有改變COUNT的值,因為子外殼中的COUNT是父外殼中COUNT的一個副本,其值的變更不影響父外殼中值。
子外殼通常用於管道的串連。使用管道命令的結果可以重新導向到子外殼中處理。這些資料似乎就是子外殼的標準輸入,如列表9.7所示:
Listing 9.7 subshell.sh
#!/bin/bash
#
# subshell.sh
#
# Perform some operation to all the files in adirectory
shopt -s -o nounset
declare -rx SCRIPT=${0##*/}
declare -rx INCOMING_DIRECTORY=”incoming”
ls -1 “$INCOMING_DIRECTORY” |
(
while read FILE ; do
printf “$SCRIPT: Processing %s...\n” “$FILE”
# <-- do something here
done
)
printf “Done\n”
exit 0
read命令一次從標準輸入讀入一行,在本執行個體中,它讀取有ls命令建立的一個檔案清單。
$ bashsubshell.sh
subshell.sh: Processing alabama_orders.txt...
subshell.sh: Processing new_york_orders.txt...
subshell.sh: Processing ohio_orders.txt...
Done
子外殼不僅僅繼承了環境變數,更詳細的內容參見第14章“函數和指令碼的執行”。
參數處理大大的增加了指令碼使用的靈活性,子外殼命令是一個不可缺少的工具。但是在指令碼真正的做到完美還有許多基礎知識需要掌握。沒有作業控制和訊號處理的指令碼仍不能稱之為完美無缺。
命令參考
getopt命令開關
--longoptions(or -l)—期望長選項使用逗號分隔的列表。
--alternative(or -a)—允許長選項只使用一個單獨的減號引導。
--quiet-output(or -Q)—檢查開關並不將處理結果返回到標準輸出中。
--quiet (or -q)—任何錯誤都不顯示出錯資訊。
--shell (or -u)—使用引號來保護特定字元。
--test ( or -T)—用於C語言相容性的測試。
來源:http://blog.csdn.net/fox_lht/article/details/7010962