如何编写PowerShell脚本批量递归重命名文件/文件夹以移除隐藏字符
解决含特殊字符的百万级文件/文件夹重命名问题
问题背景
- Windows服务器上存有近百万个文件和文件夹,文件名随机包含Apple MacOS特殊字符,导致robocopy归档时出错
- 违规特殊字符:
- 零宽空格(Zero Width Space,Unicode编码
0x200b) - U+F028 Unicode字符
- 零宽空格(Zero Width Space,Unicode编码
- 可通过Windows文件资源管理器搜索定位这类文件:
- 搜索零宽空格:
name:~"**" - 搜索U+F028:
name:~"**"
- 搜索零宽空格:
原编写的PowerShell脚本在本地小文件测试场景有效,但在外部USB驱动器的大文件集场景中失败,还存在性能隐患且缺少日志记录功能。原脚本如下:
#Set location to run from set-location -Path "P:\" #Rename folders containing Zero Width Spaces $folders = Get-ChildItem -Recurse -Directory | Sort-Object $folders | ForEach-Object { set-location $_.Parent.FullName Rename-Item $_.Name ($_.Name -replace("", "")) } #Rename folders containing U+F028 $folders = Get-ChildItem -Recurse -Directory | Sort-Object $folders | ForEach-Object { set-location $_.Parent.FullName Rename-Item $_.Name ($_.Name -replace("", "")) } #Rename files containing Zero Width Spaces $files = Get-ChildItem -File -Recurse $files | ForEach-Object { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("","") } #Rename files containing U+F028 $files = Get-ChildItem -File -Recurse $files | ForEach-Object { Rename-Item -Path $_.PSPath -NewName $_.Name.replace("","") }
优化后的脚本及说明
核心优化点
- 仅两次递归扫描(文件夹+文件),原脚本四次扫描,大幅降低IO开销,适配百万级文件场景
- 文件夹按路径深度倒序处理(先子文件夹后父文件夹),避免父文件夹重命名后子路径失效
- 用Unicode编码直接定义特殊字符,避免复制粘贴时的字符丢失问题
- 增加完整日志记录,包含操作时间、原路径、新路径及错误信息
- 无需切换工作目录,直接使用完整路径操作,避免路径错误
- 添加错误捕获,单个文件/文件夹重命名失败不会终止整个脚本
优化脚本
# 配置参数 $rootPath = "P:\" $logPath = Join-Path -Path $rootPath -ChildPath "RenameLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" # 定义要移除的特殊字符(用Unicode编码确保匹配准确) $specialChars = @([char]0x200b, [char]0xF028) $regexPattern = [regex]::Escape(-join $specialChars) # 先处理文件夹:递归遍历并按路径倒序(先子后父) Get-ChildItem -Path $rootPath -Recurse -Directory | Sort-Object -Property FullName -Descending | ForEach-Object { $newName = $_.Name -replace $regexPattern, "" if ($newName -ne $_.Name) { $newPath = Join-Path -Path $_.Parent.FullName -ChildPath $newName try { Rename-Item -Path $_.FullName -Destination $newPath -ErrorAction Stop $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | 文件夹 | 原路径: $($_.FullName) | 新路径: $newPath" Add-Content -Path $logPath -Value $logEntry Write-Host $logEntry } catch { $errorEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | 文件夹错误 | 原路径: $($_.FullName) | 错误信息: $($_.Exception.Message)" Add-Content -Path $logPath -Value $errorEntry Write-Host $errorEntry -ForegroundColor Red } } } # 处理文件:递归遍历 Get-ChildItem -Path $rootPath -Recurse -File | ForEach-Object { $newName = $_.Name -replace $regexPattern, "" if ($newName -ne $_.Name) { $newPath = Join-Path -Path $_.Parent.FullName -ChildPath $newName try { Rename-Item -Path $_.FullName -Destination $newPath -ErrorAction Stop $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | 文件 | 原路径: $($_.FullName) | 新路径: $newPath" Add-Content -Path $logPath -Value $logEntry Write-Host $logEntry } catch { $errorEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | 文件错误 | 原路径: $($_.FullName) | 错误信息: $($_.Exception.Message)" Add-Content -Path $logPath -Value $errorEntry Write-Host $errorEntry -ForegroundColor Red } } } Write-Host "重命名操作完成,日志已保存至: $logPath" -ForegroundColor Green
使用提示
- 运行前先在小范围测试文件夹验证脚本逻辑,确认无误后再全量执行
- 确保对目标驱动器拥有足够的读写权限
- 外部USB驱动器建议用USB3.0及以上接口,减少IO瓶颈影响性能
- 若文件量极大,可考虑按子文件夹分区分批处理
内容的提问来源于stack exchange,提问作者mrEdge
相关产品推荐
相关产品推荐

