首页 > 解决方案 > Bash - 递归限制子shell

问题描述

我决定在我的生产服务器上测试 bash 中的递归。

不要在生产机器上运行此代码!

#!/usr/bin/bash

function fibo {
    if [ $1 -le 1 ]; then
        echo $1
    else
        echo $((
            $( fibo $(( $1 - 1 )) ) +
            $( fibo $(( $1 - 2 )) )
        ))
    fi
}

fibo 100

我怀疑它会很慢,但是,没想到它会运行数十个并行作业。我在想,因为服务器是多核的,所以我可以终止该进程。

不幸的是,服务器冻结并从托管公司重新启动它。

有没有办法限制 bash 中的并行作业?

是否可以在计时器中设置主进程的 PID 并停止所有子作业?

当然,我只要求学术目的。

编辑

Tail 递归版本就像一个魅力:

#!/usr/bin/bash

fibo() {
    local n=$1 a=$2 b=$3

    case $n in
        0) echo "$a" ;;
        1) echo "$b" ;;
        *) fibo $((n - 1)) "$b" $((a + b)) ;;
    esac
}

fibo "${1:-10}" 0 1

基准:

time ./fibotr.sh 100

3736710778780434371

real    0m0.078s
user    0m0.000s
sys     0m0.015s

标签: bashrecursion

解决方案


至于预防方法,请参阅我们的姊妹网站Unix & Linux Stack Exchange。他们提出的热门问题之一是安全执行叉形炸弹

作为您的原始算法的实现,它根本不分叉任何子shell,因此即使传递任意大的数字也不可能变成分叉炸弹:

#!/usr/bin/env bash

declare -g _fibo_resA _fibo_resB # be explicit that it's intentional that these
                                 # variables aren't locals, for future readers
fibo() {
    local _fibo_outvar _fibo_inVal _fibo_locResA _fibo_locResB
    _fibo_outvar=$1; _fibo_inVal=$2
    if [ "$_fibo_inVal" -le 1 ]; then
        printf -v "$_fibo_outvar" 1
    else
        fibo _fibo_resA "$(( _fibo_inVal - 1 ))"; _fibo_locResA=$_fibo_resA
        fibo _fibo_resB "$(( _fibo_inVal - 2 ))"; _fibo_locResB=$_fibo_resB
        printf -v "$_fibo_outvar" "$(( _fibo_locResA + _fibo_locResB ))"
    fi
}

# note that 100 is way too big; performance breaks down between 20 and 30
# but at least it won't act like a fork bomb!
fibo result "${1:-100}"  # let user pass in a value they choose
echo "$result"

为什么要这样写?

  • 最重要的变化是消除$( ),但这不再需要在标准输出上将输出传递给父级。因此,为此目的使用间接分配
  • 使用命名空间变量名(即使是本地人!)可以防止在返回结果时按名称发生冲突:如果用户选择将结果写入与我们的任何本地人匹配的变量名,他们的值将被写入本地,而不是他们希望将结果放入的全局变量。
  • locResA并且locResB被使用是因为我们没有分叉,也不能制作resAresB本地化;fibo在我们能够返回它们之前,它们会阻止其他副本覆盖结果。
  • 使用funcname() {语法声明形式是因为function funcname {它是 ksh-ism。然而,在 ksh 中,这种格式会改变块内代码的行为;bash 不尊重这种意图。funcname() {因此,对于具有 ksh 背景的人来说,使用符合 POSIX 标准的惊喜更少,提高了您的代码与其他 POSIX shell 的兼容性;请参阅https://wiki.bash-hackers.org/scripting/obsolete(请注意,不止一处提到了函数声明语法)。

推荐阅读