You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题分析与修复

核心错误点

  1. 递归调用后未返回原目录:脚本用cd "$1"进入目标目录后,递归处理子目录时未回到原目录,导致后续循环处理的是子目录内的内容,而非原目录的剩余文件,这是递归提前终止的根本原因。
  2. 深度参数传递错误:递归调用时传递的是字符串"(( $2 + 1 ))",而非计算后的数值,导致后续递归中深度值无效,甚至引发语法错误。
  3. 遍历列表未适配重命名操作:for file in *在循环开始前生成静态文件名列表,当目录内条目被重命名后,后续迭代可能处理已不存在的文件。
  4. 错误抑制掩盖问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:15:56