递归就是函数调用自己。听起来有点绕,但有些问题用递归来解决特别自然,比如计算阶乘、遍历目录树等。
递归函数必须有两个要素:
缺少终止条件会导致无限递归,最终栈溢出。
阶乘是递归的经典例子:n! = n × (n-1) × ... × 1
factorial() {
local n=$1
# 终止条件
if [ $n -le 1 ]; then
echo 1
return
fi
# 递归调用
local prev=$(factorial $((n - 1)))
echo $((n * prev))
}
result=$(factorial 5)
echo "5! = $result"
运行结果:
5! = 120
执行过程是这样的:
斐波那契数列:1, 1, 2, 3, 5, 8, 13... 每个数是前两个数之和:
fibonacci() {
local n=$1
if [ $n -le 2 ]; then
echo 1
return
fi
local a=$(fibonacci $((n - 1)))
local b=$(fibonacci $((n - 2)))
echo $((a + b))
}
for i in {1..10}; do
result=$(fibonacci $i)
echo -n "$result "
done
echo
运行结果:
1 1 2 3 5 8 13 21 34 55
不过这个实现效率很低,因为会重复计算。fibonacci(10) 会调用 fibonacci(9) 和 fibonacci(8),而 fibonacci(9) 又会调用 fibonacci(8),重复了。实际使用时要注意性能问题。
递归遍历目录是更实用的例子:
list_files() {
local dir=$1
local indent=${2:-""}
for item in "$dir"/*; do
if [ -d "$item" ]; then
echo "${indent}📁 $(basename "$item")/"
list_files "$item" "$indent "
elif [ -f "$item" ]; then
echo "${indent}📄 $(basename "$item")"
fi
done
}
list_files "/tmp/test"
运行结果示例:
📁 dir1/
📄 file1.txt
📁 subdir/
📄 file2.txt
📁 dir2/
📄 file3.txt
dir_size() {
local dir=$1
local total=0
for item in "$dir"/*; do
if [ -f "$item" ]; then
local size=$(stat -c%s "$item" 2>/dev/null || stat -f%z "$item" 2>/dev/null)
total=$((total + size))
elif [ -d "$item" ]; then
local sub_size=$(dir_size "$item")
total=$((total + sub_size))
fi
done
echo $total
}
size=$(dir_size "/tmp/test")
echo "目录大小:$((size / 1024)) KB"
Shell 有递归深度限制,超过限制会报错:
deep_recurse() {
local n=$1
echo "深度:$n"
deep_recurse $((n + 1))
}
deep_recurse 1
运行一段时间后会报错:
深度:1
深度:2
...
深度:500
bash: deep_recurse: recursion depth exceeded (recursion depth = 500)
默认限制通常是 500 层左右,可以通过环境变量调整,但不建议设太大。
有些语言支持尾递归优化,可以把递归转换成循环。Shell 不支持,但我们可以手动转换。
递归版本:
sum_recursive() {
local n=$1
if [ $n -le 0 ]; then
echo 0
return
fi
local prev=$(sum_recursive $((n - 1)))
echo $((n + prev))
}
循环版本(更高效):
sum_loop() {
local n=$1
local total=0
local i
for ((i = 1; i <= n; i++)); do
total=$((total + i))
done
echo $total
}
能用循环解决的问题,优先用循环,性能更好,也不会有栈溢出的风险。