通常,使用花括号来定义bash函数以包围正文:
foo()
{
...
}
今天在处理shell脚本时大量使用函数时,我遇到了调用函数中调用函数名称相同的变量的问题,即这些变量是相同的。然后我发现可以通过将函数内的局部变量定义为local local var=xyz
来防止这种情况。
然后,在某些时候,我发现了一个线程(Defining bash function body using parenthesis instead of braces),其中解释说使用如下括号来定义函数同样有效:
foo()
(
...
)
这样做的结果是函数体在子shell中执行,这样做的好处是函数有自己的变量范围,这允许我在没有局部的情况下定义它们。由于具有功能本地范围似乎更有意义,并且比所有变量更全球更安全,我立即问自己:
然而,我很快发现了在子shell中执行该函数的一个主要缺点,特别是从函数内部退出脚本不再起作用,而是迫使我在整个调用中使用返回状态树(在嵌套函数的情况下)。这引出了我的后续问题:
(*)我知道(从异常相关的讨论中我偶然发现),有些人会认为明确使用错误状态远比能够退出在任何地方,但我更喜欢后者。
显然这两种风格都有其优点和缺点。所以我希望你们中有些经验丰富的bash用户可以给我一些一般指导:
编辑:从答案中删除
感谢您的回答,我的头脑现在对此有点清楚了。所以我从答案中得到的是:
坚持使用传统的花括号,只是为了不混淆脚本的潜在其他用户/开发者(如果整个身体都用括号括起来,甚至使用括号)。
花括号的唯一真正缺点是可以更改父作用域中的任何变量,尽管在某些情况下这可能是一个优点。通过将变量声明为local
。
另一方面,使用括号可能会产生一些严重的不良影响,例如搞乱退出,导致杀死脚本的问题以及隔离变量范围。
答案 0 :(得分:14)
为什么默认情况下使用大括号括起函数体而不是括号?
函数体可以是任何复合命令。这通常为{ list; }
,但技术上允许使用其他三种形式的复合命令:(list)
,((expression))
和[[ expression ]]
。
使用括号而不是大括号是否还有其他主要缺点(*)(这可能解释为什么大括号似乎更受欢迎)?
是。子shell中有许多你无法做到的事情,包括:
exit
语句将仅退出子shell。启动子shell也可能是一个严重的性能损失。每次调用函数时都会启动一个新进程。
如果您的脚本被杀死,您可能也会遇到奇怪的行为。父子shell接收的信号将发生变化。这是一个微妙的效果,但是如果你有trap
个处理程序,或者kill
你的脚本那些部分不能按照你想要的方式工作。
何时使用花括号括起函数体,何时可以切换到括号?
我建议你总是使用花括号。如果需要显式子shell,则在花括号内添加一组括号。使用括号是非常不寻常的语法,会让很多人阅读你的脚本感到困惑。
foo() {
(
subshell commands;
)
}
答案 1 :(得分:5)
真的很重要。由于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
答案 2 :(得分:4)
当我想要更改目录时,我倾向于使用子shell,但总是来自同一个原始目录,并且不能打扰使用pushd/popd
或自己管理目录。
for d in */; do
( cd "$d" && dosomething )
done
这也可以从函数体中运行,但即使你使用花括号定义函数,仍然可以从子shell中使用它。
doit() {
cd "$1" && dosomething
}
for d in */; do
( doit "$d" )
done
当然,你仍然可以使用declare或local在一个花括号定义的函数中维护变量作用域:
myfun() {
local x=123
}
所以我想说,只有当不作为子shell时才明确将函数定义为子shell,这对该函数的明显正确行为是有害的。
Trivia:作为旁注,请考虑bash实际上总是将该函数视为大括号复合命令。它有时只有括号:
$ f() ( echo hi )
$ type f
f is a function
f ()
{
( echo hi )
}
答案 3 :(得分:0)
注意:有时括号列表的运行过程不同:
a=22; { echo $a; a=46; echo $a; }; echo $a
says 22 46 46
但是
a=22; { echo $a; a=46; echo $a; }|cat; echo $a
says 22 46 22
感谢fedorqui:)