5.3.4 递归与函数作用域

4. 递归与函数作用域#

递归用于将复杂问题拆解为更小的同类问题;作用域用于控制变量可见性,避免函数间相互污染。本节以可运行脚本为主,涵盖环境准备、示例、排错与练习。


原理草图:递归调用与作用域#

文章图片

环境准备与安装#

目标工具
- bash:脚本运行环境
- tree:辅助查看目录结构
- shellcheck:脚本检查(可选)

安装命令(Debian/Ubuntu)

sudo apt-get update
sudo apt-get install -y bash tree shellcheck

安装命令(CentOS/RHEL)

sudo yum install -y bash tree shellcheck

命令解释
- apt-get install -y / yum install -y:自动安装并跳过确认。
- tree:展示目录树,便于验证递归结果。
- shellcheck:静态检查 Shell 语法与最佳实践。


递归示例:遍历目录并统计文件数#

示例脚本:/opt/scripts/rec_count.sh

#!/usr/bin/env bash
set -euo pipefail

count_files() {
  local dir="$1"
  local cnt=0
  local f

  # 递归出口:目录不存在
  [[ -d "$dir" ]] || { echo 0; return; }

  for f in "$dir"/*; do
    [[ -e "$f" ]] || continue
    if [[ -d "$f" ]]; then
      cnt=$((cnt + $(count_files "$f")))
    else
      cnt=$((cnt + 1))
    fi
  done
  echo "$cnt"
}

target_dir="${1:-/var/log}"
total=$(count_files "$target_dir")
echo "total files in $target_dir: $total"

运行与预期输出

bash /opt/scripts/rec_count.sh /etc
# 预期输出类似:
# total files in /etc: 1234

命令解释
- set -euo pipefail:遇错退出、未定义变量报错、管道错误传播。
- local:定义函数内变量,避免污染全局。
- [[ -d "$dir" ]]:判断目录存在,防止递归错误。


递归示例:计算阶乘(输入校验)#

示例脚本:/opt/scripts/fact.sh

#!/usr/bin/env bash
set -euo pipefail

is_number() {
  [[ "$1" =~ ^[0-9]+$ ]]
}

fact() {
  local n="$1"
  (( n <= 1 )) && { echo 1; return; }
  echo $(( n * $(fact $((n-1))) ))
}

n="${1:-5}"
if ! is_number "$n"; then
  echo "Error: input must be a non-negative integer" >&2
  exit 1
fi

echo "fact($n)=$(fact "$n")"

运行与预期输出

bash /opt/scripts/fact.sh 6
# 预期输出:
# fact(6)=720

函数作用域与变量管理示例#

示例脚本:/opt/scripts/scope.sh

#!/usr/bin/env bash
set -euo pipefail

name="global"
count=0

demo() {
  local name="local"
  local count=10
  echo "inside name=$name, count=$count"
}

demo
echo "outside name=$name, count=$count"

预期输出

inside name=local, count=10
outside name=global, count=0

命令解释
- local name="local":仅在函数内生效。
- 未 local 的变量会影响全局,需统一前缀命名避免冲突。


作用域 + 递归:带深度限制的遍历#

示例脚本:/opt/scripts/walk_depth.sh

#!/usr/bin/env bash
set -euo pipefail

walk() {
  local dir="$1"
  local depth="$2"
  (( depth <= 0 )) && return

  local f
  for f in "$dir"/*; do
    [[ -e "$f" ]] || continue
    echo "$f"
    [[ -d "$f" ]] && walk "$f" $((depth-1))
  done
}

walk "${1:-/var/log}" "${2:-2}"

运行与预期输出

bash /opt/scripts/walk_depth.sh /etc 1
# 仅输出 /etc 目录第一层文件/目录

排错与调试#

常见问题与解决
1. 无限递归
- 原因:缺少终止条件或输入错误。
- 处理:增加深度参数或判断目录存在。
2. “argument list too long”
- 原因:目录中文件过多,"$dir"/* 展开过大。
- 处理:用 find 替代遍历。
3. 变量污染导致结果不一致
- 原因:未使用 local
- 处理:函数内变量全部使用 local

调试命令

bash -x /opt/scripts/rec_count.sh /etc   # 跟踪执行流程
shellcheck /opt/scripts/rec_count.sh     # 静态检查

练习#

  1. 练习 1:统计目录内“可执行文件”数量
    - 要求:递归统计 -x 文件数。
    - 提示:用 [[ -x "$f" ]] 判断。

  2. 练习 2:改写阶乘为非递归
    - 要求:用 for 循环计算,避免深层递归。

  3. 练习 3:记录递归深度
    - 要求:每次递归打印“当前深度 + 路径”。

参考命令说明
- [[ -x file ]]:判断文件可执行。
- for i in {1..n}:循环累计。