使用bash編寫Linux shell指令碼–參數和子殼

來源:互聯網
上載者:User

為了成為一個靈活的工具,一個合格的指令碼必須提供額外的資訊來說明此指令碼的作用,如何執行此指令碼以及在哪兒執行此指令碼。和命令一樣指令碼也使用參數。開關和參數提高了重用性同時也減少了成本,節省了時間。

定位的參數

有三種有效方法可以使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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.