Bash递归函数无法深层遍历求助:为何同事的版本可行?
我最近写了个Shell脚本,想统计指定文件夹下的隐藏文件、符号链接和子目录总数,但递归函数只能遍历第一层级的目录,深层目录根本处理不了。我自己的代码是这样的:
function recursive_func() { current=$1 echo $current for thing in $(ls -A $current) do if [ -d $current/$thing ] then let subd++ recursive_func $current/$thing elif [ -L $thing ] ; then let symlinks++ elif [ -f $thing -a ${thing:0:1} == "." ] ; then let hidden++ fi done }
好在同事给了一个能正常工作的版本:
function recursive_func() { echo $1 for thing in $(ls -A $1) do path=$1/$thing if [ -d $path ] then let subd++ recursive_func $path elif [ -L $path ] ; then let symlinks++ elif [ -f $path -a ${thing:0:1} == "." ]; then let hidden++ fi done }
一开始我完全搞不懂两者的差异在哪,后来才发现问题出在变量作用域和路径完整性两个地方:
1. 全局变量被递归调用破坏
我的代码里,current=$1没有加local修饰,所以current是一个全局变量。当第一次调用函数处理根目录时,current被设为根目录路径;但当递归处理子目录时,current会被覆盖成子目录的路径。等递归返回回到上层循环时,current已经不是原来的根目录了,后续循环处理的$current/$thing全是错误的路径,自然没法继续遍历上层目录的其他内容。
而同事的代码根本没用到全局变量,直接用函数的参数$1表示当前目录,拼接完整路径时用$1/$thing,递归调用也直接传拼接好的$path——完全不会因为递归调用破坏上层的上下文。
2. 文件类型判断用了错误的路径
我的代码里判断符号链接和普通文件时,用的是$thing而不是$current/$thing。$thing只是当前目录下的文件名,Shell会在当前工作目录下去找这个文件,而不是我们正在遍历的$current目录,这会导致很多判断结果完全错误。比如当遍历子目录时,$thing是子目录里的文件名,但我们的工作目录还是上层目录,[ -L $thing ]肯定找不到对应的文件。
同事的代码里用了完整路径$path(也就是$1/$thing)来做判断,确保每次都是检查目标目录下的对应文件,判断结果准确。
修复我的代码的两种方式
方式一:给current加上local修饰,同时修正路径判断
function recursive_func() { local current=$1 # 把current变成函数局部变量,避免递归修改 echo $current for thing in $(ls -A $current) do full_path="$current/$thing" # 统一用完整路径 if [ -d "$full_path" ] then let subd++ recursive_func "$full_path" elif [ -L "$full_path" ] ; then let symlinks++ elif [ -f "$full_path" -a "${thing:0:1}" == "." ] ; then let hidden++ fi done }
方式二:直接参考同事的写法,依赖函数参数而非全局变量
就像同事的代码那样,全程用$1表示当前目录,拼接完整路径后做判断和递归,从根源避免全局变量的问题。
内容的提问来源于stack exchange,提问作者Gorilla

