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