本文也即《Learning the bash Shell》3rd Edition的第四章Basic Shell Programming之讀書筆記,但我們將不限於此。
運行shell指令碼程式
一個包含shell命令的指令碼就是一個shell程式,例如.bash_profile。我們建立shell指令碼,允許的時候可以通過兩種方式:一、source
;二、只敲入檔案名稱,按斷行符號就可以執行,這種方式更為便捷。我們需要將檔案放置在命令搜尋路徑下(在Linux Bash學習(六):設定環境參數
中介紹),否則需要指出絕對路徑,例如在目前的目錄,使用./,
另外我們還需要將檔案的使用權限設定為可執行檔,採用$ chmod +x
的方式設定,+x表示增加執行的許可權。
這兩種方式是由區別的。採用source就如同我們在terminal上敲入命令一樣。採用後一種方式,系統運行一個子進程,它copy了shell,叫作subshell,主shell等待子shell運行結束。另外我們設定讓子shell後台運行,即
&
。
這個區別還體現在export
的使用上,例如我們設定aa=hello, world,這個變數在命令列中有效,但是在指令檔是無效,需要使用export aa='hello, world
'
來保證在subprocess中也是有效,即可以用於指令檔中。
函數Functions
使用function有兩個原因:一、function存放在系統的記憶體,所以調用的使用,效率更高;二、更好地組織長的bash,使之模組化。定義function,有兩個方式:
function
functname
{
shell commands
}
functname
( ){
shell commands
}
上面兩種方式沒有區別。我們可以通過unset –f functname
來刪除函數的定義。 當我們定義一個function,將它的名字和定義存放在記憶體,我們可以象調用shell指令碼的方式來調用它。我們通過declare -f
來查看當前已經定義的function情況,如果我們只查看function名字,使用declare -F
。我們可以在命令列中敲入一個function來檢驗,如果我們將function定義在一個檔案中,例如在檔案a,可以用source a
來是指生效,如果我們將a設定為可執行檔,這function的定義只在該指令碼中有效,如果需要使之仍然有效,保留環境,採用$.
./a
,而不只是$./a
。
Function作為一個整體運行,不會分割為子進程,此外Function的優先順序別高於指令碼。如果有重名,優先順序別依次如下:
- Aliases
- 關鍵字,例如function,if,for
- Functions
- Built-ins,例如cd,type
- 在命令搜尋路徑PATH下的指令碼和可執行檔
如果我們需要查看所使用命令屬於哪種,用type name
,例如aa是個alias(表示pwd),同樣我們也定義了它作為一個function,根據優先順序別,aa優先作為alias,type aa,我們可以得到aa is aliased to ‘pwd’,可以用type –a(或者-all) name
,來查看aa代表的所有含義。如果使用非最優先順序別或者重新定義優先順序別的先後順序,在書的第7章,我們先放下此話題不表。上述的命令將顯示詳細內容,可以用type –t name
的方式,查看類型,將返回alias | keyword | function | builtin | file。type –p name
用於查看file的路徑,如果類型不是file將沒有返回,而type –P name
則強制尋找file的路徑。例如一個重名,它是一個alias,也是一個在PATH目錄下的可執行檔。-p則沒有返回,-P這返迴文件的絕對路徑。
位置參數
在指令碼命令中,有時是帶有參數,這些參數可以通過位置變數來擷取。例如我們有個指令檔叫做test,執行的使用帶參數,即$./test param1 param2 param3,我們可以在指令碼中來擷取參數的值。使用$N,其中$0是./test,它表示執行的指令碼名字,剩餘的為所帶參數,$1為param1,$2為param2,如此類推,如果N大於實際參數的數目,為空白。我們一般將位置變數是從$1開始。
$*
表示所有參數組成的一個字串,在上面的例子中,為param1 param2 param3。這些參數之間的間隔是IFS的首個字母即空格,IFS包括TAB,空格,換行等字元。
$@
等同於"$1" "$2"... "$N"。
$#
表示參數的個數,在上面的例子中為3。
這些位置變數都是唯讀,不能賦值的。$*和$@非常相似,一般而言輸出是一樣的。$*的分割是在IFS的字元,我們可以重新定義IFS的字元,將導致不同的輸出結果。我們在指令碼中定義IFS=,
,即可將輸出改用逗號做為分割,這個指令碼名字為a,運行./a h1 h2,則$#=2,表示有兩個參數,$@為h1 h2,$*為h1,h2
,輸出結果不一樣。
同樣這些位置變數可以用於function中,並對function的參數進行體現,即是local的,屬於function,但是$0是例外,他表示指令碼的名字,這個參數是gobal的。下面是個例子。指令碼a內容如下:
hello ()
{
var="hello";
echo "Hello"
echo "Hello: param num is $#"
echo "Hello: $@"
echo "Hello: $*"
echo "Hello: $0 $1 $2"
echo "Hello: var=$var";
}
var="hello";
echo "main: param num is $#"
echo "main: $@"
echo "main: $*"
echo "main: $0 $1 $2"
echo "main: var=$var";
hello h1 h2
我們執行./a a1 a2 a3
main: param num is 3
main: a1 a2 a3
main: a1 a2 a3
main: ./a a1 a2
main: var=main
Hello
Hello: param num is 2
Hello: h1 h2
Hello: h1 h2
Hello: ./a h1 h2
Hello: var=hello
除了$0外,所有的位置變數都是local的,即如果在function中,則在function中有效。同樣,使用者定義的其他變數,也是local的,即在最近的{}內有效。見上面例子中的使用者變數var。我們可以在function中定義var加上local來特別標明這是一個本地參數,上面的例子在函數hello,可以使用local var=”hello”
。
避免歧義的變數定義
實際上我們使用$varname,是${varname}
的一個縮寫。有時我們不能使用縮寫,例如我們需要擷取第10個參數,使用$10,這實際是第一個參數加上“0”,需要使用${10}。又例如,我們希望顯示進程號,並在後面僅跟_、字母或者數字,我們使用echo $UID_,會見UID_看作一個整體,即echo ${UID_},因此是空,需要使用echo ${UID}_,假設進程號為1000,則為1000_。如果一個變數後面緊跟著_、字母或者數字,安全起見,需要用大括弧。
相關連結:
我的Linux操作相關文章