bash函数:用大括号括住括号

通常,使用大括号来定义bash函数来封装正文:

foo()
{
    ...
}

在今天对shell脚本进行大量使用函数的工作时,我遇到了与调用函数中的名称相同的变量的问题,即这些变量是相同的。 然后我发现可以通过在函数内定义局部变量local: local var=xyz来防止这种情况。

然后,在某个时候,我发现了一个线程(使用括号而不是花括号定义bash函数体),其中解释了使用括号来定义函数同样有效:

foo()
(
    ...
)

这样做的效果是函数体是在一个子shell中执行的,这有一个好处,即该函数具有自己的变量作用域,这允许我在没有本地的情况下定义它们。 由于拥有一个函数局部范围似乎更有意义,并且比所有变量全局更安全,我立刻问自己:

  • 为什么默认使用花括号括住函数体而不是括号?
  • 然而,我很快发现了在子shell中执行函数的一个主要缺点,特别是从函数内部退出脚本不再工作,而是迫使我在整个调用树中处理返回状态(在嵌套函数)。 这导致我出现了这个后续问题:

  • 是否还有其他主要缺点(*)使用圆括号代替大括号(这可能解释为什么大括号似乎是首选)?
  • (*)我知道(从与异常相关的讨论中我发现随着时间的推移)有些人会认为明确地使用错误状态比能够从任何地方退出要好得多,但我更喜欢后者。

    显然,这两种风格都有其优点和缺点。 所以我希望你们中一些更有经验的bash用户可以给我一些一般的指导:

  • 何时应使用大括号将函数体封闭,何时建议切换到括号?
  • 编辑:采取 - 答案

    感谢您的回答,我的头现在更清晰一些。 所以我从答案中拿出的是:

  • 坚持传统的花括号,只是为了不混淆潜在的其他用户/脚本的开发人员(甚至如果整个身体被括在括号中使用括号)。

  • 花括号唯一真正的缺点是可以改变父范围中的任何变量,尽管在某些情况下这可能是一个优点。 通过将变量声明为local可以很容易地避免这种情况。

  • 另一方面,使用括号可能会产生一些严重的不良影响,例如搞乱退出,导致杀死脚本和隔离变量范围等问题。


  • 为什么默认使用花括号括住函数体而不是括号?

    函数的主体可以是任何复合命令。 这通常是{ list; } { list; } ,但在技术上允许三种其他形式的复合命令: (list)((expression))[[ expression ]]

    C语言和C ++语言,如C ++,Java,C#和JavaScript都使用大括号来分隔函数体。 花括号是熟悉这些语言的程序员最自然的语法。

    是否还有其他主要缺点(*)使用圆括号代替大括号(这可能解释为什么大括号似乎是首选)?

    是。 你无法从一个子shell中做很多事情,包括:

  • 更改全局变量。 变量更改不会传播到父shell。
  • 退出脚本。 一个exit语句只会退出子shell。
  • 启动一个子shell也可能是一个严重的性能问题。 每次调用函数时都会启动一个新进程。

    如果您的脚本被杀害,您也可能会产生奇怪的行为。 父母和小孩的信号会发生变化。 这是一个微妙的效果,但如果你有trap处理程序或你kill你的脚本,那些部分不能按你想要的方式工作。

    何时应使用大括号将函数体封闭,何时建议切换到括号?

    我建议你总是使用花括号。 如果你想要一个明确的子shell,那么在花括号里添加一组圆括号。 使用括号是非常不寻常的语法,会混淆许多人阅读你的脚本。

    foo() {
       (
           subshell commands;
       )
    }
    

    当我想更改目录时,我倾向于使用子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是不利于该功能明显的正确行为。

    琐事:作为一个侧面提示,考虑到bash实际上总是将该函数视为一个花括号复合命令。 它有时只有一个括号:

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

    这真的很重要。 由于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
    
    链接地址: http://www.djcxy.com/p/97063.html

    上一篇: bash functions: enclosing the body in braces vs. parentheses

    下一篇: bash command groups: Why do curly braces require a semicolon?