導(dǎo)讀
很多時(shí)候曙砂,我們寫的代碼并不是只運(yùn)行一次就不再用了艇拍,那就需要保存到文件里狐蜕。我們通常稱包含解釋性編程語言代碼的可執(zhí)行文件為腳本文件,簡(jiǎn)稱腳本卸夕。而在腳本內(nèi)部层释,也會(huì)有一些可以復(fù)用的代碼,我們可以把這樣的代碼寫成函數(shù)快集,供其他部分調(diào)用贡羔。Zsh 中函數(shù)和腳本基本上一樣的,可以認(rèn)為腳本就是以文件名為函數(shù)名的函數(shù)个初。腳本和函數(shù)的編寫方法基本相同乖寒,所以在一起講。
先從函數(shù)開始院溺,因?yàn)樯婕案俚募?xì)節(jié)楣嘁。
函數(shù)定義
# 一個(gè)很簡(jiǎn)單的函數(shù)
fun() {
echo good
}
# 也可以在前邊加一個(gè) function 關(guān)鍵字
function fun() {
echo good
}
這樣就可以定義一個(gè)函數(shù)了。小括號(hào)一定是空的覆获,即使函數(shù)有參數(shù)马澈,也無需在里邊寫參數(shù)列表。
直接輸入函數(shù)名即可調(diào)用函數(shù)弄息。
fun() {
echo good
}
% fun
good
用 unfunction 可以刪除函數(shù)痊班。
fun() {
echo good
}
% unfunction fun
% fun
zsh: command not found: fun
參數(shù)處理
函數(shù)可以有參數(shù),但 zsh 中無需顯式注明有幾個(gè)參數(shù)摹量,直接讀取即可涤伐。
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 個(gè)參數(shù),$# 是參數(shù)個(gè)數(shù)缨称。如果讀取的時(shí)候沒有對(duì)應(yīng)參數(shù)傳進(jìn)來凝果,那和讀取一個(gè)未定義的變量效果是一樣的。函數(shù)的參數(shù)只能是字符串類型睦尽,如果把整數(shù)器净、浮點(diǎn)數(shù)傳進(jìn)函數(shù)里,也會(huì)被轉(zhuǎn)成字符串当凡∩胶Γ可以把數(shù)組傳給函數(shù),然后數(shù)組中的元素會(huì)依次成為各個(gè)參數(shù)沿量。
fun() {
echo $1 $2 $3
echo $#
}
% array=(11 22 33)
% fun $array
11 22 33
3
這樣用的好處是可以更方便地處理帶空格的參數(shù)浪慌。
# 遍歷所有參數(shù),$* 是包含所有參數(shù)的數(shù)組
fun() {
for i ($*) {
echo $i
}
}
% fun a b c
a
b
c
可以用 $+n 快速判斷第 n 個(gè)參數(shù)是否存在朴则。
fun() {
(($+1)) && {
echo $1
}
}
關(guān)于 $* 和 $@权纤。在 bash 中, $* 和 $@ 的區(qū)別是一個(gè)比較麻煩的事情,但在 zsh 中汹想,通常沒有必要使用 $@外邓,所以不用踩這個(gè)坑。Bash 中需要使用 $@ 的原因是如果使用 $* 并且參數(shù)中有空格的話古掏,就分不清哪些空格是參數(shù)里的坐榆,哪些空格是參數(shù)之間的間隔符(bash 里的 $* 是一個(gè)字符串)。而如果使用 "$*" 的話冗茸,所有的參數(shù)都合并成一個(gè)字符串了席镀。而 "$@" 可以保留參數(shù)中的空格,所以通常使用 "$@"夏漱。但是有些時(shí)候需要把所有參數(shù)拼接成一個(gè)字符串豪诲,那么又要使用 "$*",所以很混亂挂绰。
而 zsh 中的 $* 會(huì)包括參數(shù)中的空格(zsh 里的 $* 是一個(gè)數(shù)組)屎篱,所以效果和 bash 的 "$@" 是差不多的。另外在 zsh 中用 "$*" 和在 bash 中的 "$*" 效果一樣葵蒂,所以只用 $* 和 "$*" 就足夠了交播。
函數(shù)嵌套
函數(shù)可以嵌套定義。
fun() {
fun2() {
echo $2
}
fun2 $1 $2
}
% fun aa bb
bb
fun2 函數(shù)是在 fun 執(zhí)行過才會(huì)被定義的践付,但最外邊也能直接訪問 fun2 函數(shù)秦士。如果想要最外邊訪問不了,可以在 fun 結(jié)束前調(diào)用 unfunction fun2 刪除 fun2 函數(shù)永高。
返回值
函數(shù)需要返回一個(gè)代表函數(shù)是否正確執(zhí)行的返回值隧土,如果是 0,代表正確執(zhí)行命爬,如果不是 0曹傀,代表有錯(cuò)誤。
#!/bin/zsh
fun() {
(($+1)) && {
return
}
return 1
}
% fun 111 && echo good
good
% fun || echo bad
bad
% fun
# 也可以用 $? 獲取函數(shù)返回值
% echo $?
遇到 return 后饲宛,函數(shù)立即結(jié)束皆愉。return 即 return 0。
注意返回值不是用來返回?cái)?shù)據(jù)的艇抠,如果函數(shù)需要將字符串幕庐、整數(shù)、浮點(diǎn)數(shù)等返回給調(diào)用者练链,直接用 echo 或者 print 等命令輸出即可翔脱,然后調(diào)用者用 $(fun) 獲取奴拦。如果需要返回?cái)?shù)組或者哈希表媒鼓,只能通過變量(全局變量或者函數(shù)所在層次的局部變量)傳遞。
fun() {
echo 123.456
}
% echo $($(fun) *2))
246.91200000000001
通過全局變量返回。
array=()
fun() {
array=(aa bb)
}
% fun
% echo $array
aa bb
局部變量
在函數(shù)中可以直接讀寫函數(shù)外邊的變量绿鸣,并且在函數(shù)中定義的新變量在函數(shù)退出后依然存在疚沐。
str1=abcd
fun() {
echo $str1
str2=1234
}
% fun
abcd
% echo $str2
1234
這通常是不符合預(yù)期的。為了避免函數(shù)內(nèi)的變量“滲透”到函數(shù)外潮模,可以使用局部變量亮蛔,使用 local 定義變量。
str1=abcd
fun() {
echo $str1
local str2=1234
}
% fun
abcd
% echo $str2
函數(shù)中的變量擎厢,除非確實(shí)需要留給外部使用究流,不然最好全部使用局部變量,避免引發(fā) bug动遭。
腳本
可以認(rèn)為腳本也是一個(gè)函數(shù)芬探,但它是單獨(dú)寫到一個(gè)文件里的。
test.zsh 內(nèi)容厘惦。
#!/bin/zsh
echo good
這是一個(gè)非常簡(jiǎn)單的腳本文件偷仿。第一行是固定的,供系統(tǒng)找到 zsh 解釋器宵蕉,#! 后加 zsh 的絕對(duì)路徑即可酝静。如果需要使用環(huán)境變量訪問,可以用 #!/bin/env zsh (或者 !/usr/bin/env zsh羡玛,如果 env 在 /usr/bin/ 里邊)别智。
從第二行開始,就和函數(shù)中的內(nèi)容一樣了稼稿。上邊函數(shù)體里的內(nèi)容(去掉首尾行的 fun() { 和 }亿遂,都可以寫在這里邊。
執(zhí)行的話渺杉,在 test.zsh 所在目錄蛇数,運(yùn)行 zsh test.zsh 加參數(shù)即可(就像調(diào)用了一個(gè)名為 zsh test.zsh 的函數(shù)。也可以 chmod u+x test.zsh 給它添加可執(zhí)行權(quán)限后是越,直接運(yùn)行 ./test.zsh 加參數(shù)耳舅。
腳本的參數(shù)和返回值的處理方法,和函數(shù)的完全一樣倚评,這里就不舉例了浦徊。
但函數(shù)和腳本中執(zhí)行的時(shí)候是有區(qū)別的,函數(shù)是在當(dāng)前的 zsh 進(jìn)程里執(zhí)行(也可以調(diào)用的時(shí)候加小括號(hào)在子進(jìn)程執(zhí)行)天梧,而腳本是在新的子進(jìn)程里執(zhí)行盔性,執(zhí)行完子進(jìn)程即退出了,所以腳本中的變量值外界是訪問不到的呢岗,無需使用 local 定義(使用也沒問題)冕香。
exit 命令
腳本可以使用 return 返回蛹尝,也可以使用 exit 命令。exit 命令用法和 return 差不多悉尾,如果不加參數(shù)則返回 0突那。但在代碼的任何地方,調(diào)用 exit 命令即退出腳本构眯,即使是在一個(gè)嵌套很深的函數(shù)里邊理調(diào)用的愕难。
用 getopts 命令處理命令行選項(xiàng)
有時(shí)我們寫的腳本需要支持比較復(fù)雜的命令行選項(xiàng),比如 demo -i aa -t bb -cx ccc ddd惫霸,這樣的話猫缭,手動(dòng)處理就會(huì)很麻煩∫嫉辏可以使用內(nèi)置的 getopts 命令饵骨。
#!/bin/zsh
# i: 代表可以接受一個(gè)帶參數(shù)的 -i 選項(xiàng)
# c 代表可以接受一個(gè)不帶參數(shù)的 -c 選項(xiàng)
while {getopts i:t:cv arg} {
case $arg {
(i)
# $OPTARG 存放選項(xiàng)對(duì)應(yīng)的參數(shù)
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 指向剩下的第一個(gè)未處理的參數(shù)
echo $*[$OPTIND,-1]
運(yùn)行結(jié)果:
% ./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
# 可以只加部分選項(xiàng)
% ./demo -i aaa -v bbb ccc
i option with arg: aaa
version: 0.1
bbb ccc
# 可以一個(gè)選項(xiàng)也不加
% ./demo aaa bbb
aaa bbb
# 如果選項(xiàng)不帶參數(shù),多個(gè)選項(xiàng)可以合并到一個(gè) - 后
% ./demo -i aaa -cv bbb ccc
i option with arg: aaa
c option
version: 0.1
bbb ccc
# 如果該帶參數(shù)的選項(xiàng)不帶參數(shù)茫打,會(huì)報(bào)錯(cuò)
% ./demo -i aaa -t
i option with arg: aaa
./demo:3: argument expected after -t option
error
# 加了不支持的選項(xiàng)也會(huì)報(bào)錯(cuò)
% ./demo -i aaa -a bbb ccc
i option with arg: aaa
./demo:3: bad option: -a
error
# 如果該帶參數(shù)的選項(xiàng)不帶參數(shù)居触,然后后邊緊接著另一個(gè)選項(xiàng),那么選項(xiàng)會(huì)被當(dāng)作參數(shù)
% ./demo -i -c aaa bbb
i option with arg: -c
aaa bbb
getopts 的使用還是很方便的老赤,但它不支持長(zhǎng)選項(xiàng)(如 --log aaa)轮洋。如果需要使用長(zhǎng)選項(xiàng),可以用 getopt 命令抬旺,它是一個(gè)外部命令弊予,可以 man getopt 查看用法。
總結(jié)
本文簡(jiǎn)單介紹了函數(shù)和腳本的寫法开财,重點(diǎn)是參數(shù)處理和返回值等等汉柒,還有很多沒覆蓋的地方,以后可能繼續(xù)補(bǔ)充责鳍。
參考
https://my.oschina.net/lenglingx/blog/410565
更新歷史
20170901:增加用 $? 獲取函數(shù)返回值的內(nèi)容碾褂。
20170902:增加“用 getopts 命令處理命令行選項(xiàng)”。
本文不再更新历葛,全系列文章在此更新維護(hù):github.com/goreliu/zshguide
付費(fèi)解決 Windows正塌、Linux、Shell恤溶、C乓诽、C++、AHK咒程、Python鸠天、JavaScript、Lua 等領(lǐng)域相關(guān)問題帐姻,靈活定價(jià)稠集,歡迎咨詢奶段,微信 ly50247。