Bash递归重命名脚本异常终止问题求助
问题:Bash递归重命名脚本异常终止
需求说明
给定目录路径,递归将所有子目录和文件按规则重命名:
- 每个目录内的子目录依次命名为
dir1、dir2…… - 每个目录内的文件依次命名为
file1、file2…… - 向每个文件中写入其相对于起始目录的深度
- 每个目录内的文件计数器和目录计数器独立重置
测试目录结构
原目录结构
testdir/ ├── jfjfjddsdsdsds (directory) ├── jfjfjfjf (file) ├── kdskskk ├── kkkkkkkkk ├── sadadasad │ ├── assd │ ├── kakak (directory) │ ├── ksja (directory) │ ├── lklksl (directory) │ └── lskal └── skdjsjlsj (directory) ├── djfldjlfjd (directory) │ ├── eiuru │ ├── rhhrhshs │ ├── rhrhshs │ └── shhdshsd ├── jjskskk ├── kdjskdjfks (directory) ├── kjjrjjrj └── kjskjd
期望重命名后结构
testdir/ ├── dir1 (directory) ├── file1 ├── file2 ├── file3 ├── dir2 (directory) │ ├── file1 │ ├── dir1 (directory) │ ├── dir2 (directory) │ ├── dir3 (directory) │ └── file2 └── dir3 (directory) ├── dir1 (directory) │ ├── file1 │ ├── file2 │ ├── file3 │ └── file4 ├── file1 ├── dir2 (directory) ├── file2 └── file3
现有脚本
#!/bin/bash #see executed comands when run set -xeuo pipefail rename() { # $1 - directory path, $2 - directory depth fileCounter=1 dirCounter=1 #enter a directory cd "$1" for file in * do if [[ -f "$file" ]] then #if its a regular file, write its depth and rename it echo "(I am file, my depth is $2)" > "$file" mv "$file" "file$fileCounter" 2>/dev/null #increase the counter (( fileCounter++ )) elif [[ -d "$file" ]] then #its a directory - rename and call recursion mv "$file" "dir$dirCounter" 2>/dev/null #call recursion with depth + 1 rename "dir$dirCounter" "(( $2 + 1 ))" #increase the counter (( dirCounter++ )) #else #do nothing, its not a regular file or a directory fi done } if [[ $# -gt 1 ]] then echo "Too many arguments >:(" exit 1 fi if [[ ! -d "$1" ]] then echo "The directory doesn't exist" exit 1 fi #start recursion rename "$1" 0
执行问题
脚本执行后递归提前终止,上层目录的文件也未完全处理:
执行输出片段
+ [[ 1 -gt 1 ]] + [[ ! -d testdir/ ]] + rename testdir/ 0 + fileCounter=1 + dirCounter=1 + cd testdir/ + for file in * + [[ -f jfjfjddsdsdsds ]] + echo '(I am file, my depth is 0)' + mv jfjfjddsdsdsds file1 + (( fileCounter++ )) + for file in * + [[ -f jfjfjfjf ]] + echo '(I am file, my depth is 0)' + mv jfjfjfjf file2 + (( fileCounter++ )) + for file in * + [[ -f kdskskk ]] + [[ -d kdskskk ]] + mv kdskskk dir1 + rename dir1 '(( 0 + 1 ))' + fileCounter=1 + dirCounter=1 + cd dir1 + for file in * + [[ -f * ]] + [[ -d * ]] + (( dirCounter++ )) + for file in * + [[ -f kkkkkkkkk ]] + [[ -d kkkkkkkkk ]] + for file in * + [[ -f sadadasad ]] + [[ -d sadadasad ]] + for file in * + [[ -f skdjsjlsj ]] + [[ -d skdjsjlsj ]]
最终目录结构
testdir/ ├── dir1 ├── file1 ├── file2 ├── kkkkkkkkk ├── sadadasad │ ├── assd │ ├── kakak │ ├── ksja │ ├── lklksl │ └── lskal └── skdjsjlsj ├── djfldjlfjd │ ├── eiuru │ ├── rhhrhshs │ ├── rhrhshs │ └── shhdshsd ├── jjskskk ├── kdjskdjfks ├── kjjrjjrj └── kjskjd
问题分析与修复
核心错误点
- 递归调用后未返回原目录:脚本用
cd "$1"进入目标目录后,递归处理子目录时未回到原目录,导致后续循环处理的是子目录内的内容,而非原目录的剩余文件,这是递归提前终止的根本原因。 - 深度参数传递错误:递归调用时传递的是字符串
"(( $2 + 1 ))",而非计算后的数值,导致后续递归中深度值无效,甚至引发语法错误。 - 遍历列表未适配重命名操作:
for file in *在循环开始前生成静态文件名列表,当目录内条目被重命名后,后续迭代可能处理已不存在的文件。 - 错误抑制掩盖问题:
mv命令后加了2>/dev/null,掩盖了误将目录当作文件处理时的写入错误,导致流程混乱。
修正后的脚本
#!/bin/bash set -xeuo pipefail rename() { local dir_path="$1" local depth="$2" local fileCounter=1 local dirCounter=1 # 使用pushd进入目录,后续用popd返回,避免目录切换混乱 pushd "$dir_path" &>/dev/null || return 1 # 先收集所有条目,避免重命名导致的遍历异常 local entries=(*) for entry in "${entries[@]}" do if [[ -f "$entry" && ! -L "$entry" ]] then # 写入深度信息 echo "(I am file, my depth is $depth)" > "$entry" # 重命名文件 mv "$entry" "file$fileCounter" ((fileCounter++)) elif [[ -d "$entry" && ! -L "$entry" ]] then # 先记录新目录名,重命名后递归处理 local new_dir="dir$dirCounter" mv "$entry" "$new_dir" # 递归调用,传递计算后的深度值 rename "$new_dir" $((depth + 1)) ((dirCounter++)) fi done # 返回原目录 popd &>/dev/null || return 1 } if [[ $# -gt 1 ]] then echo "参数过多 >:(" exit 1 fi if [[ ! -d "$1" ]] then echo "指定的目录不存在" exit 1 fi # 启动递归 rename "$1" 0
修正说明
- 目录切换管理:用
pushd和popd替代直接cd,确保递归处理完子目录后回到原目录,避免后续循环处理错误的目录内容。 - 正确传递深度参数:用
$((depth + 1))计算新深度,传递数值而非字符串表达式。 - 静态收集条目:先将目录内所有条目存入数组
entries,避免循环过程中目录内容变化导致的遍历异常。 - 排除符号链接:判断文件/目录时增加
! -L "$entry",避免误处理符号链接(可根据需求调整)。 - 移除不必要的错误抑制:去掉
mv后的2>/dev/null,方便排查错误,若需要静默处理可再加回。
内容的提问来源于stack exchange,提问作者timohahaa
相关产品推荐
相关产品推荐

