2015-01-06 78 views
21

通常情況下,Bash函數定義使用大括號括身體:bash的功能:封閉體在括號與括號

foo() 
{ 
    ... 
} 

在shell腳本工作今天在企業廣泛應用的功能,我我們遇到了與調用函數中調用名稱相同的變量問題,即那些變量是相同的。然後我發現可以通過在本地函數中定義局部變量來防止這種情況:local var=xyz

然後,在某些時候,我發現在它被解釋說,它只是爲有效使用括號像這樣來定義一個函數線程(Defining bash function body using parenthesis instead of braces):

foo() 
(
    ... 
) 

這樣做的效果是函數體是在一個子shell中執行的,這有利於函數具有自己的變量作用域,這使我可以在沒有本地的情況下定義它們。由於具有功能本地範圍似乎使更多的意義,並要安全得多比所有變量是全球性的,我馬上問自己:

  • 爲什麼默認情況下用於封閉函數體,而不是括號的括號?

不過,我很快也發現了一個重大的缺點在子shell執行的功能,特別是從函數內退出腳本不工作了,而不是強迫我沿着返回狀態工作整個調用樹(在嵌套函數的情況下)。這使我這個跟進的問題:

  • 是否有其他重大缺點(*)使用括號,而不是括號(這或許可以解釋爲什麼括號似乎是優選)?

(*)我知道(在異常相關的討論,我在一段時間偶然),一些將明確使用錯誤狀態認爲是比能夠從任何地方退出好得多,但我更喜歡後者。

顯然這兩種款式都有其優點和缺點。所以我希望你們中的一些有經驗的bash用戶可以給我一些一般性的指導:

  • 我什麼時候用大括號括函數體,當是最好切換到括號?

編輯:從答案

謝謝您的回答,我的頭現在關於這更清楚的一個位外賣店。所以,我從答案帶走的是:

  • 固守傳統的花括號,如果僅僅是爲了不混淆潛在的其他用戶/開發者的腳本(甚至使用大括號如果整個身體包裹在括號內)。

  • 花括號的唯一真正的缺點是父範圍中的任何變量都可以改變,儘管在某些情況下這可能是一個優點。通過聲明變量爲local可以很容易地避免這種情況。

  • 另一方面,使用括號可能會產生一些嚴重的不良影響,例如搞亂退出,導致殺死腳本和隔離變量作用域等問題。

+0

您的編輯是一個很好的包裝! – fedorqui

回答

12

爲什麼默認情況下用於封閉函數體,而不是括號括號?

函數的主體可以是任何複合命令。這通常是{ list; },但在技術上允許其他三種形式的複合命令:(list),((expression))[[ expression ]]

C和C系列中的語言(如C++,Java,C#和JavaScript)都使用大括號來分隔函數體。花括號是熟悉這些語言的程序員最自然的語法。

使用括號代替大括號(這可能解釋爲什麼大括號似乎是首選)還有其他主要缺點(*)嗎?

是的。有很多東西你不能從子殼做到,包括:

  • 更改全局變量。變量更改不會傳播到父shell。
  • 退出腳本。一條exit語句只會退出子shell。

啓動一個子shell也可能是一個嚴重的性能問題。每次調用函數時都會啓動一個新進程。

如果您的腳本被殺死,您也可能會產生奇怪的行爲。父母和小孩的信號會發生變化。這是一個微妙的影響,但如果你有trap處理程序或你kill你的腳本那些部分不按你想要的方式工作。

何時應使用大括號將函數體括起來,何時建議切換到括號?

我會建議你總是使用花括號。如果你想要一個明確的子shell,那麼在花括號裏添加一組圓括號。使用括號是非常不尋常的語法,會混淆許多人閱讀你的腳本。

foo() { 
    (
     subshell commands; 
    ) 
} 
+0

雖然我首先考慮了@ kojiro's,但我會接受你的答案,因爲你明確地給了回答所有三個問題,再加上如果腳本被殺死(不會想到這一點),並且使用不帶括號的圓括號將會是非常不尋常的synthax,那麼您已經提出了與堰行爲有關的兩個非常好的觀點。 – flotzilla

+0

但我仍然看到能夠立即更改全局變量,但必須明確聲明局部變量,而不是加上更多的危險。我非常希望這些變量默認爲本地變量,但能夠將它們聲明爲全局變量,以便仍然可以訪問這些變量。但我會養成習慣,總是用「本地」來宣佈它們來解決這個問題。 – flotzilla

+1

@VaticanViolator我同意,如果他們默認是本地的,會更好。這只是你學會接受和處理的事情之一。例如,我用小寫和全局變量命名爲大寫本地變量。它清楚地表明,如果我忘記了一個「本地」聲明,哪些是意外地踐踏全局變量的風險和降低風險。 –

4

真的很重要。由於bash函數不返回值,並且它們使用的變量來自全局作用域(即,它們可以從其作用域的「外部」訪問變量),所以處理函數輸出的常用方法是將值存儲在一個變量然後調用它。

當你用()定義一個函數時,你是對的:它會創建子shell。該子shell將包含原始值相同的值,但無法修改它們。這樣你就失去了改變全局範圍變量的資源。

看到一個例子:

$ cat a.sh 
#!/bin/bash 

func_braces() { #function with curly braces 
echo "in $FUNCNAME. the value of v=$v" 
v=4 
} 

func_parentheses() (
echo "in $FUNCNAME. the value of v=$v" 
v=8 
) 


v=1 
echo "v=$v. Let's start" 
func_braces 
echo "Value after func_braces is: v=$v" 
func_parentheses 
echo "Value after func_parentheses is: v=$v" 

讓我們來執行它:

$ ./a.sh 
v=1. Let's start 
in func_braces. the value of v=1 
Value after func_braces is: v=4 
in func_parentheses. the value of v=4 
Value after func_parentheses is: v=4 # the value did not change in the main shell 
+1

技術上的函數可以通過'return'返回一個退出代碼值,這將通過鏈接或'$?'返回到父類。 – Catskul

4

我傾向於使用一個子shell的時候我想改變目錄,但總是從同一原始目錄,而不能懶得使用pushd/popd或自己管理目錄。

for d in */; do 
    (cd "$d" && dosomething) 
done 

這從一個函數體以及工作,但即使你定義用大括號的功能,它仍然有可能從一個子shell使用。

doit() { 
    cd "$1" && dosomething 
} 
for d in */; do 
    (doit "$d") 
done 

當然,你仍然可以保持一個大括號定義函數裏面的變量範圍使用聲明或地方:

myfun() { 
    local x=123 
} 

所以,我要說,明確地定義你的功能只有一個子shell 而不是是一個子外殼對該函數明顯的正確行爲是有害的。

瑣事:作爲一個附註,請考慮bash實際上總是將該函數視爲花括號複合命令。它只是有時中有括號:

$ f() (echo hi) 
$ type f 
f is a function 
f() 
{ 
    (echo hi) 
}