本篇文章介紹定義函數的兩種格式和一些使用經驗:git
在 bash 中,定義函數時,function 關鍵字是可選的,查看man bash手冊,裏面提到定義函數的兩種格式以下:shell
name () compound-command [redirection]
function name [()] compound-command [redirection]
從中能夠看到,當不寫 function 關鍵字時,函數名後面必定要跟着小括號()
,而寫了 function 關鍵字時,小括號是可選的。bash
關於 compound-command 的說明,一樣能夠查看 man bash 手冊,裏面提到下面幾種形式:less
A compound command is one of the following:
(list) list is executed in a subshell environment. Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.
{ list; } list is simply executed in the current shell environment. list must be terminated with a newline or semicolon. The return status is the exit status of list.
常見的是 { list; }
這種形式,可是寫爲 (list)
也能夠。舉例以下:ide
$ testpwd() (pwd) $ testpwd /home/sample/ $ testpwd() ( pwd ) $ testpwd /home/sample/ $ testpwd() (pwd;) $ testpwd /home/sample/
這裏定義了一個 testpwd 函數,它自身的代碼是用小括號()
括起來的 pwd 命令,這個命令跟 ()
之間能夠有空格,也能夠沒有空格。在命令後能夠加分號,也能夠不加分號。函數
注意:使用 { list; }
這個寫法時,在 list 後面必定要跟着分號';',不然會報錯。並且 list;
和左邊的大括號 {
之間要有空格。若是寫爲 {list;}
會報錯,而 { list;}
不會報錯,建議仍是寫爲 { list; }
的形式。舉例以下:ui
$ lsfunc() {ls} -bash: syntax error near unexpected token `{ls}' $ function lsfunc() {ls;} -bash: syntax error near unexpected token `{ls' $ lsfunc() { ls;} $ lsfunc hello.c
調用 bash shell 的函數時,不須要寫小括號()
,只寫函數名便可。指針
例如執行上面的 lsfunc() 函數,直接寫 lsfunc
就能夠。若是寫成 lsfunc()
反而變成從新定義這個函數。調試
在函數名後面能夠提供要傳入的參數列表,不一樣參數之間用空格隔開。
若是某個參數須要帶有空格,要用引號把該參數括起來。rest
Bash 要求函數的返回值必須爲一個整數,不能用 return 語句返回字符串變量。
通常來講,該整數返回值爲 0,表示函數執行成功,非0 表示執行失敗。
在自定義的函數裏面,執行 return 語句會退出函數,不會退出整個腳本。
在函數裏面執行 exit 語句則會退出整個腳本,而不是隻退出函數。
因爲在函數內部用 return 返回,只能返回整數。
若是想從函數內部把字符串傳遞到函數以外,能夠用 echo 命令來實現,就是在函數內部打印字符串,而後調用者獲取標準輸出獲取到打印的字符串。
具體舉例以下:
$ function foo() { echo "foo"; return 0; } $ var=$(foo) $ echo ${var}, $? foo, 0
能夠看到,打印結果是 "foo, 0"。
此時看起來,這個函數像是返回了兩個值,一個是經過 $(foo)
獲取 foo 函數的標準輸出,另外一個是 $?
會獲取函數經過 return 語句返回的 0。
若是在函數中寫爲 return 1
,那麼上面的 $?
打印的出來的值是 1。
下面再舉例說明以下:
$ foo() { echo "foo"; } $ bar() { foo; } $ foobar() { a=$(foo); } $ var=$(foo); echo first: ${var} first: foo $ var=$(bar); echo second: ${var} second: foo $ var=$(foobar); echo third: ${var} third:
能夠看到, foo 函數將字符串寫到標準輸出,var=$(foo);
語句把 foo 函數的標準輸出賦值給 var 變量,打印 var 變量的值是 "foo"。
bar() 函數調用了 foo 函數,可是沒有讀取 foo 函數打印的標準輸出,則這個標準輸出會被 bar 函數繼承,就好象這個標準輸出是由 bar 函數輸出同樣,var=$(bar);
語句也會把 var 變量賦值爲 "foo"。
而 foobar 函數讀取了 foo 函數的標準輸出,foobar 函數自身沒有用 echo 命令來輸出內容,此時再經過 $(foobar)
來獲取該函數的輸出,會獲取到空,由於 foo 函數中的標準輸出給 foobar 讀走了。
注意:這種在函數內部經過 echo 命令輸出字符串的作法有個缺陷,就是不能再在函數裏面執行 echo 語句來打印調試信息,這些調試信息會被函數外的語句一塊兒讀取到,有用的結果和調試信息都混在一塊兒,若是函數外的語句沒有打印這些結果,就會看不到調試信息。
執行某個函數後,可使用 $?
表達式來獲取函數的 return 返回值,可是要注意下面的一種狀況:
var=$(foo) if [ "$?" == "0" ]; then echo success fi
此時,不要在 var=$(foo)
和 if [ "$?" == "0" ]; then
之間添加任何語句。
不然,$?
獲取到將不是 $(foo)
的 return 值,判斷就有問題,特別是不要添加 echo 調試語句。
換句話來講,這種先執行一個語句,再判斷 $?
的方法不是很可靠,會受到各類影響,要特別注意代碼語句的順序。
在 bash 中,能夠經過以下的方式來達到相似C語言函數指針的效果。
假設有一個 test.sh
腳本,內容以下:
#!/bin/bash # 注意$1後面有一個分號';', 少這個分號會報錯 echo_a() { echo aaaa $1; } echo_b() { echo bbbb $1; } if [ "$1" == "-a" ]; then # 這裏的 echo_a 沒有加雙引號 echo_common=echo_a elif [ "$1" == "-b" ]; then # 上面的echo_a沒加雙引號, 這裏加了. # 實際上, 可加可不加, 均可以正確執行. echo_common="echo_b" else echo ERROR; exit 1 fi ${echo_common} common
這個腳本經過 echo_common 變量來保存函數名,至關因而函數指針,再經過 ${echo_common}
來調用它保存的函數。
在 bash shell 中執行 ./test.sh -a
命令,會輸出 "aaaa common";執行 ./test.sh -b
命令,會輸出 "bbbb common"。
在函數裏面執行 cd 命令,切換到某個目錄後,函數退出時,當前工做目錄仍是會保持在那個目錄,而不會自動恢復爲原先的工做目錄,須要手動執行 cd -
命令再切換回去。
假設有一個 testcd.sh
腳本,裏面的內容以下:
#!/bin/bash echo "now, the pwd is: $(pwd)" cd_to_root() { cd /usr/; } cd_to_root echo "after execute the cd_to_root, pwd is: $(pwd)"
這個函數先打印出執行腳本時工做目錄路徑,而後執行自定義的 cd_to_root 函數,在函數內部切換工做目錄到 "/usr/",最後在 cd_to_root 函數外面打印工做目錄路徑。
執行 ./testcd.sh
腳本,會輸出下面的內容:
[~/sample]$ ./testcd.sh now, the pwd is: /home/sample after execute the cd_to_root, pwd is: /usr [~/sample]$ pwd /home/sample
能夠看到,若是在函數裏面執行過 cd 命令,函數退出後,當前工做目錄仍是 cd 後的目錄。
可是腳本執行結束後,當前 shell 的工做目錄仍是以前的工做目錄,不是腳本里面 cd 後的目錄。
在每一個 shell 下面,當前工做目錄 (working directory ) 是全局的狀態,一旦改變,在整個 shell 裏面都會改變。
而 bash 執行腳本時,是啓動一個新的子 shell 來執行,因此腳本內部執行 cd 命令,會影響運行這個腳本的子 shell 的工做目錄,但不影響原先父 shell 的工做目錄。
在 bash 中,沒有使用 local
命令來聲明的變量都是全局變量,在函數內部定義的變量也是全局變量。
若是沒有注意到這一點,在函數內操做變量可能會影響到的外部同名變量,形成不預期的結果。
爲了不對外部同名變量形成影響,函數內的變量最好聲明爲局部變量,使用 local
命令來聲明。查看 man bash 裏面對 local
命令的說明以下:
local [option] [name[=value] ...]
For each argument, a local variable named name is created, and assigned value. The option can be any of the options accepted by declare.
When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children. With no operands, local writes a list of local variables to the standard output.
It is an error to use local when not within a function.
The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.
即,在函數內,使用 local 命令聲明的變量是局部變量,這些變量在函數外不可見。
在函數外,不能使用 local 命令聲明變量,不然會報錯。
注意:如上面說明,local
命令自己會返回一個值,正常的返回值是 0。假設有個 testlocal.sh
腳本,內容以下:
#!/bin/bash foo() { return 1; } bar() { ret=$(foo); echo first: $? local var=$(foo); echo second: $? } foobar() { return 0; } bar local out=$(foobar); echo third: $?
則執行 ./testlocal.sh
腳本,會輸出:
first: 1 second: 0 ./testlocal.sh: 第 x 行:local: 只能在函數中使用 third: 1
能夠看到,ret=$(foo);
語句沒有使用 local 命令來定義 ret 變量,執行 foo 函數,該函數的返回值是 1,因此獲得的 $?
是 1。
而 local var=$(foo);
語句使用 local 命令來定義 var 變量,執行 foo 函數,獲得的 $?
倒是 0,而foo 函數明明返回的是 1。
緣由就是該語句先經過 $(foo)
執行 foo 函數,而後用 local var
來定義 var 變量爲局部變量,因此該語句對應的 $?
是 local 命令的返回值,而不是 foo 函數的返回值。
當在函數外執行 local 命令時,它會報錯,能夠看到雖然 foobar 函數返回是 0,可是第三個 echo 語句打印的 $?
是 1,正好是 local 命令執行出錯時的返回值。
即,對於 local var=$(func);
這種語句來講,它對應的 $?
不是所調用函數的返回值,而是local 命令的返回值。
因此執行函數後,想用 $?
來判斷函數的 return 返回值時,注意不要用這種寫法。
爲了不這種狀況,最好在函數開頭就用 local 命令聲明全部局部變量。
使用 local 聲明多個變量時,變量之間用空格隔開,不要加逗號。舉例以下:
local a b c;
在 bash 中,可使用 $1
、$2
、$3
、...、$n
的方式來引用傳入函數的參數,$1
對應第一個參數值,$2
對應第二個參數值,依次類推。
若是 n 的值大於 9,那麼須要用大括號{}
把 n 括起來。
例如,${10}
表示獲取第 10 個參數的值,寫爲 $10
獲取不到第 10 個參數的值。
實際上,$10
至關於 ${1}0
,也就是先獲取 $1
的值,後面再跟上 0,若是 $1
的值是 "first",則 $10
的值是 "first0"。
下面經過一個 testparams.sh
腳原本舉例說明,該腳本的內容以下:
#!/bin/bash function show_params() { echo $1 , $2 , $3 , $4 , $5 , $6 , $7 , $8 , $9 , $10 , ${10} , ${11} } show_params $@
這個腳本把傳入自身的參數傳給 show_params 函數,該函數再打印出各個參數,使用 $10
、${10}
這兩個形式來講明它們的區別。
執行 ./testparams.sh
腳本的結果以下:
$ ./testparams.sh 1a 2b 3c 4d 5e 6f 7g 8h 9i 10j 11k 1a , 2b , 3c , 4d , 5e , 6f , 7g , 8h , 9i , 1a0 , 10j , 11k
能夠看到,傳入的第 10 個參數值是 "10j",而 $10
打印出來的結果是 "1a0",也就是第一個參數 "1a" 後面再跟上 0。 ${10}
打印的結果纔是第 10 個參數的值。
相應地,${11}
也能正確打印第 11 個參數的值。
$1
、$2
這種寫法在 bash 文檔裏面稱之爲 positional parameter,中文直譯過來是 「位置參數」。
查看 man bash 裏面的說明以下:
Positional Parameters
A positional parameter is a parameter denoted by one or more digits, other than the single digit 0. Positional parameters are assigned from the shell's arguments when it is invoked, and may be reassigned using the set builtin command.
Positional parameters may not be assigned to with assignment statements. The positional parameters are temporarily replaced when a shell function is executed.
When a positional parameter consisting of more than a single digit is expanded, it must be enclosed in braces.${parameter}
The value of parameter is substituted.
The braces are required when parameter is a positional parameter with more than one digit, or when parameter is followed by a character which is not to be interpreted as part of its name.
The parameter is a shell parameter or an array reference (Arrays).
能夠看到,這裏面提到了須要用大括號{}
把大於9的數字括起來,{}
的做用是限定大括號裏面的字符串是一個總體。
例如,有一個 var 變量值是 "Test",如今想打印這個變量值,並跟着打印 "Hello" 字符串,也就是打印出來 "TestHello" 字符串,那麼獲取 var 變量值的語句和 "Hello" 字符串中間就不能有空格,不然 echo 命令會把這個空格一塊兒打印出來,可是寫爲 $varHello
達不到想要的效果。
具體舉例以下:
$ var="Test" $ echo $var Hello Test Hello $ echo $varHello $ echo ${var}Hello TestHello
能夠看到,$var Hello
這種寫法打印出來的 "Test" 和 "Hello" 中間有空格,不是想要的結果。
而 $varHello
打印爲空,這實際上是獲取 varHello 變量的值,這個變量沒有定義過,默認值是空。
${var}Hello
打印出了想要的結果,用大括號 {}
把 var 括起來,明確指定要獲取的變量名是 var,避免混淆。
上面貼出的 testparams.sh
腳本代碼裏面還用到了一個 $@
特殊參數,它會擴展爲 positional parameter 自身的列表。
查看 man bash 的說明以下:
Special Parameters
@ Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word.
That is, "$@
" is equivalent to "$1
" "$2
" ...
注意:$@
和 "$@"
這兩種寫法獲得的結果可能會有所不一樣。$@
是擴展爲 $1
$2
...,而 "$@"
是擴展爲 "$1"
"$2"
...
修改上面的 testparams.sh
腳原本舉例說明 $@
和 "$@"
的區別:
#!/bin/bash function show_params() { echo $1 , $2 , $3 } show_params $@ show_params "$@"
執行 testparams.sh
腳本,輸出結果以下:
$ ./testparams.sh 1a 2b 3c 1a , 2b , 3c 1a , 2b , 3c $ ./testparams.sh "1 a" "2 b" "3 c" 1 , a , 2 1 a , 2 b , 3 c $ ./testparams.sh 1a 2b 1a , 2b , 1a , 2b ,
能夠看到,當傳入腳本的參數值不帶空格時,$@
和 "$@"
獲得的結果相同。
當傳入腳本的參數值自身帶有空格時,$@
獲得的參數個數會變多,"$@"
能夠保持參數個數不變。
上面的 $1
是 "1 a",$@
會拆分紅 "1" "2" 兩個參數,而後傳給 show_params 函數;"$@"
會保持爲 "1 a" 不變,而後傳給 show_params 函數。
即,"1 a" "2 b" "3 c"
這三個參數,通過 $@
處理後,獲得的是 "1" "a" "2" "b" "3" "c"
六個參數。
通過 "$@"
處理後,獲得的仍是 "1 a" "2 b" "3 c"
三個參數。
同時也看到,即便只傳入兩個參數,引用 $3
也不會報錯,只是會獲取爲空。