我正在尝试在 shell 脚本中编写递归函数。考虑以下代码:
function printA {
if [[ "$1" = 0 ]]; then
return
else
echo "a$(printA $(("$1" - 1)))"
fi
}
printA 10
function factorial {
if [[ "$1" = 0 ]]; then
return 1
else
return $(( "$1" * $(factorial $(( $1 - 1 )) ) ))
fi
}
echo $(factorial 5)
代码失败:
bash
(3.0)
recur.sh:第 5 行:“10”- 1:语法错误:需要操作数(错误标记为“”10”- 1”)
A
recur.sh: 第 16 行: "1" * : 语法错误: 需要操作数 (错误标记为 ""1" * ")
recur.sh: 第 16 行: "2" * : 语法错误: 需要操作数 (错误标记为 ""2" * ")
recur.sh:第 16 行:“3”*:语法错误:需要操作数(错误标记为“”3”*”)
recur.sh: 第 16 行: "4" * : 语法错误: 需要操作数 (错误标记为 ""4" * ")
recur.sh: 第 16 行: "5" * : 语法错误: 需要操作数 (错误标记为 ""5" * ")
zsh
(4.2.1)
printA:1: 错误的数学表达式: 非法字符: "
A
阶乘:5:错误的数学表达式:非法字符:“
然而,它部分成功地使用了ksh88
.只有第二个函数失败:
啊啊啊啊啊
recur.sh[5]: 1 * : 预计有更多令牌
recur.sh[5]: 2 * : 预计有更多令牌
recur.sh[5]: 3 * : 预计有更多令牌
recur.sh[5]: 4 * : 预计有更多令牌
recur.sh[5]: 5 * : 预计有更多令牌
- 我做错了什么吗?
- bash 和 zsh 是否支持另一种递归语法?
- 为什么第二个函数 (
factorial
) 失败ksh
?
聚苯乙烯:我知道,递归是邪恶的,性能很差,我应该使用常规循环代替,bla bla bla。我不是在讨论递归是好是坏,而是讨论普通的shell是否支持它。当简单的迭代循环就可以解决问题时,我还没有愚蠢到在生产中发送递归函数:)
答案1
- 语法错误:算术运算中不使用引号。
- 逻辑错误:您正在混合 STDOUT 和
return
值。
将值作为 STDOUT 传递:
function factorial {
(( $1 )) &&
echo $(( $1 * $( factorial $(( $1 - 1 )) ) )) ||
echo 1
}
factorial 5
或者return
他们:
function factorial {
(( $1 )) || return 1
factorial $(( $1 - 1 ))
return $(( $1 * $? ))
}
factorial 5
echo $?
两个代码都可以在bash
, ksh
(当然是 93,不知道 88)和中工作zsh
,所以我想是的,shell 确实支持递归。
答案2
递归并不是邪恶的;只要您了解调用函数时内部发生的情况以及陷阱即可。
首先,确保有一个停止条件,该条件将在递归函数完成其任务时执行。如果不这样做,你就没有递归函数,而是无循环。
接下来是变量和重入点。每次调用函数都会将信息推送到堆栈上;重进入点的地址(函数返回时下一条指令的地址)。然后你必须为tge返回值类型保留空间。
接下来是范围问题。变量作为参数传递,局部变量在函数中声明。每次调用函数时,都必须在堆栈上分配此空间,并一直保留在那里,直到返回调用函数时弹出。因此,最终您将耗尽堆栈内存(堆栈溢出情况)。
我为在 DEC Vax 上教授的 Pascal 课程编写了“河内塔”程序。我试图创造一个条件,通过添加我想要的任意数量的杆和环,使我的程序或 VMS 崩溃。我在 3 根杆子上完成了 1000 圈,程序仍然运行。跑了大约10分钟,但是ir跑了。
无论如何,回到你的问题。这里发生的事情似乎是你的变量处于错误的范围内 - 看起来它们是在全局环境中调用的,而不是本地环境中。因此,函数对变量所做的任何更改都会反映在函数的所有实例中。所有更改都需要在本地范围内进行,然后将值返回到调用函数。我不确定像 bash 这样的解释语言是否支持局部变量,并且可以使每个变量对所有脚本的逻辑都可见。