如果函数在子 shell 中,它的行为会如何变化?

How does the behavior of a function change if it is within a subshell?

在脚本中创建函数时 bash,似乎人们经常在子 shell 中使用函数 运行,即

function(){(

)}

而不是

function(){

}

如果有的话,使用 {()} 而不仅仅是 {} 的 benefit/downsides 是什么?

如果在 (..) 子 shell 中调用 exit,它只会终止该表达式。此外,代码可以自由更改变量的值以及全局选项(通过 set);这些更改在周围的代码中看不到,并且在表达式退出时消失,这可以简化对代码正确性的推理。

当您在函数内部使用 (...) 时,请注意这个陷阱:(...) 内部的 return 命令不会从函数中 return;它只会终止 (...),就像 exit 一样。如果您在 (...) 之后有命令,它们将随后执行。

圆括号导致函数 运行 在子 shell 中,这是一个与父 shell 隔离的子进程。当您想在不影响函数外部代码行为的情况下更改 process-wide 环境时,它们很有用。

示例包括:

  • 将当前目录更改为cd不会影响父目录shell。 subshell 中的 运行 cdpushdpopd.

    的更简洁的替代方案
  • 变量赋值与子shell隔离。您可以临时更改 $PATH$IFS 等全局设置,而无需在前后仔细保存和恢复它们的值。

  • Shell选项改成setshopt时会自动恢复subshell 退出。例如,我通常写 (set -x; some-commands) 来临时启用命令日志记录。

  • trap一起安装的信号处理程序仅在子shell中有效。您可以在函数运行期间安装自定义 INT (Ctrl-C) 处理程序,或将自定义 EXIT 处理程序安装到 运行函数 returns.

    时的清理代码
    func() {(
        echo 'entering func' >&2
        trap 'echo exiting func >&2' EXIT
    
        ...
    )}
    
  • 如果调用 exit,它不会导致整个脚本退出。如果您想从调用堆栈中的几个函数调用 exit 作为一种穷人的“异常”,这将很有用。

    或者,如果您想获取一个可能会退出的脚本,将其包装在一个 subshell 中将防止它杀死您的脚本。

    (
        . ./script-that-might-exit
        echo "script set $foo to $foo"
        echo "script changed dir to $PWD"
    )
    


有趣的事实:函数不必用大括号分隔。省略大括号并使用圆括号作为分隔符是合法的:

func() (
    # runs in a subshell
)