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

導讀

不少時候,咱們寫的代碼並非只運行一次就再也不用了,那就須要保存到文件裏。咱們一般稱包含解釋性編程語言代碼的可執行文件爲腳本文件,簡稱腳本。而在腳本內部,也會有一些能夠複用的代碼,咱們能夠把這樣的代碼寫成函數,供其餘部分調用。Zsh 中函數和腳本基本上同樣的,能夠認爲腳本就是以文件名爲函數名的函數。腳本和函數的編寫方法基本相同,因此在一塊兒講。git

先從函數開始,由於涉及更少的細節。github

函數定義

# 一個很簡單的函數
fun() {
    echo good
}

# 也能夠在前邊加一個 function 關鍵字
function fun() {
    echo good
}複製代碼

這樣就能夠定義一個函數了。小括號必定是空的,即便函數有參數,也無需在裏邊寫參數列表。編程

直接輸入函數名便可調用函數。數組

fun() {
    echo good
}

% fun
good複製代碼

用 unfunction 能夠刪除函數。bash

fun() {
    echo good
}

% unfunction fun
% fun
zsh: command not found: fun複製代碼

參數處理

函數能夠有參數,但 zsh 中無需顯式註明有幾個參數,直接讀取便可。微信

fun() {
    echo $1 $2 $3
    echo $#
}

% fun aa
aa
1
% fun aa bb cc
aa bb cc
3
% fun aa bb cc dd
aa bb cc
4複製代碼

$n 是第 n 個參數,$# 是參數個數。若是讀取的時候沒有對應參數傳進來,那和讀取一個未定義的變量效果是同樣的。函數的參數只能是字符串類型,若是把整數、浮點數傳進函數裏,也會被轉成字符串。能夠把數組傳給函數,而後數組中的元素會依次成爲各個參數。編程語言

fun() {
    echo $1 $2 $3
    echo $#
}

% array=(11 22 33)
% fun $array
11 22 33
3複製代碼

這樣用的好處是能夠更方便地處理帶空格的參數。ide

# 遍歷全部參數,$* 是包含全部參數的數組
fun() {
    for i ($*) {
        echo $i
    }
}

% fun a b c
a
b
c複製代碼

能夠用 $+n 快速判斷第 n 個參數是否存在。函數

fun() {
    (($+1)) && {
        echo $1
    }
}複製代碼

關於 $* 和 $@。在 bash 中, $* 和 $@ 的區別是一個比較麻煩的事情,但在 zsh 中,一般沒有必要使用 $@,因此不用踩這個坑。Bash 中須要使用 $@ 的緣由是若是使用 $* 而且參數中有空格的話,就分不清哪些空格是參數裏的,哪些空格是參數之間的間隔符(bash 裏的 $* 是一個字符串)。而若是使用 "$*" 的話,全部的參數都合併成一個字符串了。而 "$@" 能夠保留參數中的空格,因此一般使用 "$@"。可是有些時候須要把全部參數拼接成一個字符串,那麼又要使用 "$*",因此很混亂。ui

而 zsh 中的 $* 會包括參數中的空格(zsh 裏的 $* 是一個數組),因此效果和 bash 的 "$@" 是差很少的。另外在 zsh 中用 "$*" 和在 bash 中的 "$*" 效果同樣,因此只用 $* 和 "$*" 就足夠了。

函數嵌套

函數能夠嵌套定義。

fun() {
    fun2() {
        echo $2
    }

    fun2 $1 $2
}

% fun aa bb
bb複製代碼

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

返回值

函數須要返回一個表明函數是否正確執行的返回值,若是是 0,表明正確執行,若是不是 0,表明有錯誤。

#!/bin/zsh 
fun() {
    (($+1)) && {
        return
    }

    return 1
}

% fun 111 && echo good
good
% fun || echo bad
bad

% 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 $array
aa bb複製代碼

局部變量

在函數中能夠直接讀寫函數外邊的變量,而且在函數中定義的新變量在函數退出後依然存在。

str1=abcd

fun() {
    echo $str1
    str2=1234
}

% fun
abcd
% echo $str2
1234複製代碼

這一般是不符合預期的。爲了不函數內的變量「滲透」到函數外,可使用局部變量,使用 local 定義變量。

str1=abcd

fun() {
    echo $str1
    local str2=1234
}

% fun
abcd
% echo $str2複製代碼

函數中的變量,除非確實須要留給外部使用,否則最好所有使用局部變量,避免引起 bug。

腳本

能夠認爲腳本也是一個函數,但它是單獨寫到一個文件裏的。

test.zsh 內容。

#!/bin/zsh 
echo 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]

# 或者用 shift 把以前用過的參數移走
# shift $((OPTIND - 1))
# echo $*複製代碼

運行結果:

% ./demo -i aaa -t bbb -cv ccc ddd
i option with arg: aaa
t option with arg: bbb
c option
version: 0.1
ccc ddd

# 能夠只加部分選項
% ./demo -i aaa -v bbb ccc
i option with arg: aaa
version: 0.1
bbb ccc

# 能夠一個選項也不加
% ./demo aaa bbb
aaa bbb

# 若是選項不帶參數,多個選項能夠合併到一個 - 後
% ./demo -i aaa -cv bbb ccc
i option with arg: aaa
c option
version: 0.1
bbb ccc

# 若是該帶參數的選項不帶參數,會報錯
% ./demo -i aaa -t
i option with arg: aaa
./demo:3: argument expected after -t option
error

# 加了不支持的選項也會報錯
% ./demo -i aaa -a bbb ccc
i option with arg: aaa
./demo:3: bad option: -a
error

# 若是該帶參數的選項不帶參數,而後後邊緊接着另外一個選項,那麼選項會被看成參數
% ./demo -i -c aaa bbb
i option with arg: -c
aaa bbb複製代碼

getopts 的使用仍是很方便的,但它不支持長選項(如 --log aaa)。若是須要使用長選項,能夠用 getopt 命令,它是一個外部命令,能夠 man getopt 查看用法。

總結

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

參考

my.oschina.net/lenglingx/b…

全系列文章地址:github.com/goreliu/zsh…

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

相關文章
相關標籤/搜索