【问题标题】:How to stop bash from creating subshells when recursively call a function递归调用函数时如何阻止bash创建子shell
【发布时间】:2023-04-03 08:10:01
【问题描述】:

这是一个计算阶乘的简单 shell 函数。

#!/bin/bash

function factorial()
{
   if (( $1 < 2 ))
   then
     echo 1
   else
     echo $(( $1 * $(factorial $(( $1 - 1 ))) ))
   fi
}

factorial $1

但我发现当输入非常大时,该脚本会创建许多子 shell。它没有必要也没有效率。有什么方法可以调用递归函数而不创建新的子shell?

我的目标不是如何在 shell 中编写阶乘函数,而是如何在调用递归定义的函数时避免创建子 shell。

事实上,即使是简单的函数调用也会导致创建子shell:

#!/bin/bash

function fac0() {
  ps >> log
  echo $1
}

function fac1() {
  ps >> log
  echo $(( $1 * $(fac0 $(( $1 - 1 ))) ))
}

touch log
fac1 $1

运行脚本后,log 文件的内容是:(它仍然会创建子 shell)

  PID TTY          TIME CMD
 9205 pts/8    00:00:00 bash
 9245 pts/8    00:00:00 call_subshell.s
 9247 pts/8    00:00:00 ps
  PID TTY          TIME CMD
 9205 pts/8    00:00:00 bash
 9245 pts/8    00:00:00 call_subshell.s
 9248 pts/8    00:00:00 call_subshell.s
 9249 pts/8    00:00:00 ps

由于子外壳创建,存在其他不需要的方面。

#!/bin/bash

declare -i i
i=0

function factorial() {
   i=$(($i + 1))
   if (( $1 < 2 ))
   then
     echo 1
   else
     local c=$(( $1 - 1 ))
     echo $(( $1 * $(factorial $c) ))
   fi
}

factorial $1
echo $i

无论参数是多少,此脚本都会打印$i=1

【问题讨论】:

  • 命令替换总是启动一个新的子shell;你无法改变它。
  • 函数调用不创建子shell。手册说:“函数在当前 shell 的上下文中执行;没有创建新的进程来解释它们”。但是,您使用“$(...)”。
  • 根据手册,确实如此。但是当我不小心尝试计算“阶乘 500”时,我也很惊讶,系统监视器显示了很长的子进程列表。
  • 请注意,使用$(…) 需要子shell(但$((…)) 不需要)。

标签: bash shell recursion process


【解决方案1】:

避免递归:

#!/bin/bash
factorial() {
    local f=1
    for ((i=2; i<=$1; i++)) ; do
        (( f *= i ))
    done
    echo $f
}

factorial $1

子shell 是由于命令替换而产生的。使用“参数作为结果”的方式来避免它:

#!/bin/bash

# set -xv

factorial() {
    # echo $BASH_SUBSHELL
    if [[ $2 ]] ; then
        if (( $2 == 1 )) ; then
            echo $1
        else
            factorial $(( $1 * $2 )) $(( $2 - 1 ))
        fi
    else
        factorial $1 $(( $1 - 1 ))
    fi
}

factorial $1

【讨论】:

  • 谢谢,但我的目标不是如何在shell中编写阶乘函数,而是在调用递归定义的函数时如何避免创建子shell。
  • 你的回答很棒。但是,根据您的“子shell是由于命令替换而产生的。使用“作为结果的参数”的方式来避免它”,似乎shell脚本注定要创建子shell并且当它有模式时相对较慢比如“$(function args...)”或“$(command args...)”。 shell编程真可惜。 (:-
  • @TorosFanny:是的,对于编程,使用 programming 语言。例如,Perl 与 bash 非常相似。
【解决方案2】:

只需将计算与函数调用分开并使用'let'内置函数。如果参数中有命令,请使用内置的“eval”而不是 $()。也许没有那么优雅,但避免了子shell。

if [[ $2 ]] ; then
    if (( $2 == 1 )) ; then
        echo $1
    else
        let "p1=$1*$2"
        let "p2 = $2-1"
        factorial $p1 $p2
    fi
else
    let "p2 = $1-1"
    factorial $1 $p2
fi

【讨论】:

    猜你喜欢
    • 2016-10-23
    • 2015-04-13
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2017-07-08
    • 2010-12-16
    相关资源
    最近更新 更多