Zsh 開發指南(第九篇 函數和腳本)

導讀

很多時候,我們寫的代碼並不是只運行一次就不再用了,那就需要保存到文件里。我們通常稱包含解釋性編程語言代碼的可執行文件為腳本文件,簡稱腳本。而在腳本內部,也會有一些可以復用的代碼,我們可以把這樣的代碼寫成函數,供其他部分調用。Zsh 中函數和腳本基本上一樣的,可以認為腳本就是以文件名為函數名的函數。腳本和函數的編寫方法基本相同,所以在一起講。

先從函數開始,因為涉及更少的細節。

函數定義

# 一個很簡單的函數fun() { echo good}# 也可以在前邊加一個 function 關鍵字function fun() { echo good}

這樣就可以定義一個函數了。小括弧一定是空的,即使函數有參數,也無需在裡邊寫參數列表。

直接輸入函數名即可調用函數。

fun() { echo good}% fungood

用 unfunction 可以刪除函數。

fun() { echo good}% unfunction fun% funzsh: command not found: fun

參數處理

函數可以有參數,但 zsh 中無需顯式註明有幾個參數,直接讀取即可。

fun() { echo $1 $2 $3 echo $#}% fun aaaa1% fun aa bb ccaa bb cc3% fun aa bb cc ddaa bb cc4

$n 是第 n 個參數,$# 是參數個數。如果讀取的時候沒有對應參數傳進來,那和讀取一個未定義的變數效果是一樣的。函數的參數只能是字元串類型,如果把整數、浮點數傳進函數里,也會被轉成字元串。可以把數組傳給函數,然後數組中的元素會依次成為各個參數。

fun() { echo $1 $2 $3 echo $#}% array=(11 22 33)% fun $array11 22 333

這樣用的好處是可以更方便地處理帶空格的參數。

# 遍歷所有參數,$* 是包含所有參數的數組fun() { for i ($*) { echo $i }}% fun a b cabc

可以用 $+n 快速判斷第 n 個參數是否存在。

fun() { (($+1)) && { echo $1 }}

關於 $* 和 $@。在 bash 中, $* 和 $@ 的區別是一個比較麻煩的事情,但在 zsh 中,通常沒有必要使用 $@,所以不用踩這個坑。Bash 中需要使用 $@ 的原因是如果使用 $* 並且參數中有空格的話,就分不清哪些空格是參數里的,哪些空格是參數之間的間隔符(bash 里的 $* 是一個字元串)。而如果使用 "$*" 的話,所有的參數都合併成一個字元串了。而 "$@" 可以保留參數中的空格,所以通常使用 "$@"。但是有些時候需要把所有參數拼接成一個字元串,那麼又要使用 "$*",所以很混亂。

而 zsh 中的 $* 會包括參數中的空格(zsh 里的 $* 是一個數組),所以效果和 bash 的 "$@" 是差不多的。另外在 zsh 中用 "$*" 和在 bash 中的 "$*" 效果一樣,所以只用 $* 和 "$*" 就足夠了。

函數嵌套

函數可以嵌套定義。

fun() { fun2() { echo $2 } fun2 $1 $2}% fun aa bbbb

fun2 函數是在 fun 執行過才會被定義的,但最外邊也能直接訪問 fun2 函數。如果想要最外邊訪問不了,可以在 fun 結束前調用 unfunction fun2 刪除 fun2 函數。

返回值

函數需要返回一個代表函數是否正確執行的返回值,如果是 0,代表正確執行,如果不是 0,代表有錯誤。

#!/bin/zshfun() { (($+1)) && { return } return 1}% fun 111 && echo goodgood% fun || echo badbad% fun# 也可以用 $? 獲取函數返回值% echo $?

遇到 return 後,函數立即結束。return 即 return 0。

注意返回值不是用來返回數據的,如果函數需要將字元串、整數、浮點數等返回給調用者,直接用 echo 或者 print 等命令輸出即可,然後調用者用 $(fun) 獲取。如果需要返回數組或者哈希表,只能通過變數(全局變數或者函數所在層次的局部變數)傳遞。

fun() { echo 123.456}% echo $($(fun) *2))246.91200000000001

通過全局變數返回。

array=()fun() { array=(aa bb)}% fun% echo $arrayaa bb

局部變數

在函數中可以直接讀寫函數外邊的變數,並且在函數中定義的新變數在函數退出後依然存在。

str1=abcdfun() { echo $str1 str2=1234}% funabcd% echo $str21234

這通常是不符合預期的。為了避免函數內的變數「滲透」到函數外,可以使用局部變數,使用 local 定義變數。

str1=abcdfun() { echo $str1 local str2=1234}% funabcd% echo $str2

函數中的變數,除非確實需要留給外部使用,不然最好全部使用局部變數,避免引發 bug。

腳本

可以認為腳本也是一個函數,但它是單獨寫到一個文件里的。

test.zsh 內容。

#!/bin/zshecho good

這是一個非常簡單的腳本文件。第一行是固定的,供系統找到 zsh 解釋器,#! 後加 zsh 的絕對路徑即可。如果需要使用環境變數訪問,可以用 #!/bin/env zsh (或者 !/usr/bin/env zsh,如果 env 在 /usr/bin/ 裡邊)。

從第二行開始,就和函數中的內容一樣了。上邊函數體里的內容(去掉首尾行的 fun() { 和 },都可以寫在這裡邊。

執行的話,在 test.zsh 所在目錄,運行 zsh test.zsh 加參數即可(就像調用了一個名為 zsh test.zsh 的函數。也可以 chmod u+x test.zsh 給它添加可執行許可權後,直接運行 ./test.zsh 加參數。

腳本的參數和返回值的處理方法,和函數的完全一樣,這裡就不舉例了。

但函數和腳本中執行的時候是有區別的,函數是在當前的 zsh 進程里執行(也可以調用的時候加小括弧在子進程執行),而腳本是在新的子進程里執行,執行完子進程即退出了,所以腳本中的變數值外界是訪問不到的,無需使用 local 定義(使用也沒問題)。

exit 命令

腳本可以使用 return 返回,也可以使用 exit 命令。exit 命令用法和 return 差不多,如果不加參數則返回 0。但在代碼的任何地方,調用 exit 命令即退出腳本,即使是在一個嵌套很深的函數裡邊理調用的。

用 getopts 命令處理命令行選項

有時我們寫的腳本需要支持比較複雜的命令行選項,比如 demo -i aa -t bb -cx ccc ddd,這樣的話,手動處理就會很麻煩。可以使用內置的 getopts 命令。

#!/bin/zsh# i: 代表可以接受一個帶參數的 -i 選項# c 代表可以接受一個不帶參數的 -c 選項while {getopts i:t:cv arg} { case $arg { (i) # $OPTARG 存放選項對應的參數 echo $arg option with arg: $OPTARG ;; (t) echo $arg option with arg: $OPTARG ;; (c) echo $arg option ;; (v) echo version: 0.1 ;; (?) echo error return 1 ;; }}# $OPTIND 指向剩下的第一個未處理的參數echo $*[$OPTIND,-1]

運行結果:

% ./demo -i aaa -t bbb -cv ccc dddi option with arg: aaat option with arg: bbbc optionversion: 0.1ccc ddd# 可以只加部分選項% ./demo -i aaa -v bbb ccci option with arg: aaaversion: 0.1bbb ccc# 可以一個選項也不加% ./demo aaa bbbaaa bbb# 如果選項不帶參數,多個選項可以合併到一個 - 後% ./demo -i aaa -cv bbb ccci option with arg: aaac optionversion: 0.1bbb ccc# 如果該帶參數的選項不帶參數,會報錯% ./demo -i aaa -ti option with arg: aaa./demo:3: argument expected after -t optionerror# 加了不支持的選項也會報錯% ./demo -i aaa -a bbb ccci option with arg: aaa./demo:3: bad option: -aerror# 如果該帶參數的選項不帶參數,然後後邊緊接著另一個選項,那麼選項會被當作參數% ./demo -i -c aaa bbbi option with arg: -caaa bbb

getopts 的使用還是很方便的,但它不支持長選項(如 --log aaa)。如果需要使用長選項,可以用 getopt 命令,它是一個外部命令,可以 man getopt 查看用法。

總結

本文簡單介紹了函數和腳本的寫法,重點是參數處理和返回值等等,還有很多沒覆蓋的地方,以後可能繼續補充。

參考

my.oschina.net/lengling

更新歷史

20170901:增加用 $? 獲取函數返回值的內容。

20170902:增加「用 getopts 命令處理命令行選項」。

本文不再更新,全系列文章在此更新維護:github.com/goreliu/zshguide

付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活定價,歡迎諮詢,微信 ly50247。


推薦閱讀:

TAG:zsh | Shell編程 | Shell語言 |