实用脚本如何实现批量修改文件名编码?

wen 实用脚本 2

本文目录导读:

实用脚本如何实现批量修改文件名编码?

  1. 方法一:使用 convmv(Linux/macOS 最推荐)
  2. 方法二:使用 Python 脚本(跨平台通用)
  3. 方法三:PowerShell 脚本(Windows 专属)
  4. 常见问题与调试技巧

这是一个非常实用的需求,文件名编码问题通常出现在从旧系统(如Windows简体中文GBK)、不同语言系统或不同服务器迁移文件时,导致文件名出现乱码(如Îĵµ.txt文档.txt)。

以下是几种主流的批量修改文件名编码的实用脚本方法,按推荐程度排序:

使用 convmv(Linux/macOS 最推荐)

convmv 是一个专为转换文件名编码设计的工具,非常安全(默认只预览,不修改)。

安装

# Debian/Ubuntu
sudo apt install convmv
# macOS (Homebrew)
brew install convmv
# CentOS/RHEL
sudo yum install convmv

常用脚本示例

场景1: 从 GBK 转换成 UTF-8 (最常见)

# 先在当前目录递归预览(强烈建议先做这一步)
convmv -f gbk -t utf-8 --notest ./*
# -f 源编码  -t 目标编码  --notest 执行实际转换(去掉则只预览)
# Windows 用户如果遇到带BOM的文件名,可能需要将目标指定为 utf8-bom
# convmv -f gbk -t utf-8-bom --notest ./*

场景2: 从 EUC-JP(日文) 转换成 UTF-8

convmv -f eucjp -t utf-8 --notest ./*

场景3: 没有 convmv 时,可以用 iconv 配合 rename

# 适用于少量文件,且需要手动处理循环
for file in *; do
  new=$(echo "$file" | iconv -f gbk -t utf-8)
  if [ "$file" != "$new" ]; then
    mv "$file" "$new"
  fi
done

使用 Python 脚本(跨平台通用)

这是最灵活、最强大的方法,可以处理复杂的编码探测和自定义逻辑。

脚本: batch_fix_filename_encoding.py

#!/usr/bin/env python3
import os
import sys
import argparse
def decode_filename(name, src_encoding, target_encoding='utf-8'):
    """
    尝试将文件名从 src_encoding 解码(修正乱码),再重编码为 target_encoding
    如果文件名本身就包含无效字符,需要先按二进制处理。
    """
    try:
        # 方法1:假设文件名已经是乱码,尝试用源编码解码它
        # 注意:这里需要处理文件名本身的 bytes 形式
        name_bytes = name.encode('latin-1')  # 先转成 bytes(latin-1 无损耗)
        decoded_name = name_bytes.decode(src_encoding)
        return decoded_name
    except (UnicodeDecodeError, UnicodeEncodeError):
        try:
            # 方法2:如果文件名本身是正常的 unicode 但字符编码不对
            return name.encode('latin-1').decode(src_encoding)
        except:
            return name  # 无法处理则跳过
def batch_convert(root_dir, src_encoding='gbk', target_encoding='utf-8', dry_run=True):
    """
    批量递归转换文件名编码
    """
    for dirpath, dirnames, filenames in os.walk(root_dir):
        # 处理文件
        for filename in filenames:
            old_path = os.path.join(dirpath, filename)
            new_name = decode_filename(filename, src_encoding, target_encoding)
            if new_name != filename:
                new_path = os.path.join(dirpath, new_name)
                if dry_run:
                    print(f"[试运行] {old_path} -> {new_path}")
                else:
                    try:
                        os.rename(old_path, new_path)
                        print(f"[已转换] {old_path} -> {new_path}")
                    except Exception as e:
                        print(f"[失败] {old_path}: {e}")
        # 处理目录名(需要小心递归顺序,建议先处理文件,再处理目录)
        for dirname in dirnames:
            old_dirpath = os.path.join(dirpath, dirname)
            new_dirname = decode_filename(dirname, src_encoding, target_encoding)
            if new_dirname != dirname:
                new_dirpath = os.path.join(dirpath, new_dirname)
                if dry_run:
                    print(f"[试运行] 目录 {old_dirpath} -> {new_dirpath}")
                else:
                    try:
                        os.rename(old_dirpath, new_dirpath)
                        print(f"[已转换] 目录 {old_dirpath} -> {new_dirpath}")
                    except Exception as e:
                        print(f"[失败] 目录 {old_dirpath}: {e}")
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='批量修正文件名编码乱码')
    parser.add_argument('path', nargs='?', default='.', help='要处理的目录路径(默认当前目录)')
    parser.add_argument('-f', '--from', dest='src_encoding', default='gbk', help='原始文件名编码(如gbk、shift_jis、euc-kr)')
    parser.add_argument('-t', '--to', dest='target_encoding', default='utf-8', help='目标编码')
    parser.add_argument('--force', action='store_true', help='强制执行(默认只预览)')
    args = parser.parse_args()
    root_dir = args.path
    src = args.src_encoding
    target = args.target_encoding
    dry_run = not args.force
    print(f"源编码: {src} -> 目标编码: {target}")
    print(f"试运行模式: {dry_run}")
    batch_convert(root_dir, src, target, dry_run)
    if dry_run:
        print("✅ 预览完成,如果无误,请加 --force 参数执行实际转换。")
    else:
        print("✅ 转换完成!")

用法:

# 预览(推荐)
python batch_fix_filename_encoding.py ./乱码目录/ -f gbk
# 实际转换
python batch_fix_filename_encoding.py ./乱码目录/ -f gbk --force

PowerShell 脚本(Windows 专属)

Windows 上的文件名通常是 UTF-16(系统内部),但如果你是从 FTP 或旧系统获取的文件,可能会显示为乱码。

PowerShell 脚本:Fix-Encoding.ps1

<#
.SYNOPSIS
批量修复文件名编码(适用于 Windows 下从 FTP 或其他系统下载的乱码文件)
.DESCRIPTION
将乱码文件名从指定编码重新编码为 UTF-8/GBK
#>
param(
    [string]$Path = ".",
    [string]$FromEncoding = "GBK",
    [bool]$DryRun = $true
)
function Convert-FileNameToBytes {
    param([string]$name)
    # 将当前文件名(可能是乱码)视为某个编码的字节流
    # 注意:PowerShell 中文件名实际是 .NET 字符串,需要先取得原始字节
    # 这个方法依赖 Windows 行为,可能需要调整
    $bytes = [System.Text.Encoding]::GetEncoding(28591).GetBytes($name)  # Latin-1
    return $bytes
}
function Decode-FileName {
    param([string]$name, [string]$fromEnc)
    try {
        $bytes = Convert-FileNameToBytes $name
        $newName = [System.Text.Encoding]::GetEncoding($fromEnc).GetString($bytes)
        return $newName
    } catch {
        return $name
    }
}
$items = Get-ChildItem -Path $Path -Recurse
foreach ($item in $items) {
    $newName = Decode-FileName $item.Name $FromEncoding
    if ($newName -ne $item.Name) {
        $newPath = Join-Path $item.DirectoryName $newName
        if ($DryRun) {
            Write-Host "[预览] $($item.FullName) -> $newPath" -ForegroundColor Yellow
        } else {
            try {
                Rename-Item -Path $item.FullName -NewName $newName
                Write-Host "[已转换] $($item.FullName) -> $newPath" -ForegroundColor Green
            } catch {
                Write-Host "[失败] $($item.FullName) : $_" -ForegroundColor Red
            }
        }
    }
}
if ($DryRun) {
    Write-Host "`n✅ 预览完成,若要执行转换,请设置 -DryRun:$false" -ForegroundColor Cyan
}

用法:

# 预览
.\Fix-Encoding.ps1 -Path "D:\乱码目录" -FromEncoding GBK
# 执行
.\Fix-Encoding.ps1 -Path "D:\乱码目录" -FromEncoding GBK -DryRun:$false

常见问题与调试技巧

  1. 如何确定源编码?

    • 如果乱码看起来像 Îĵµ.txt,源编码通常是 GBK(或 GB18030)。
    • 如果乱码看起来像 文档.txt,源编码通常是 UTF-8(但被系统当成了拉丁文或GBK显示)。
    • 如果乱码看起来像 或 锟斤拷,说明多次错误转换,通常需要先转换为 UTF-8,再转换为 GBK 处理。
    • 可以使用 chardet(Python库)自动探测:
      # pip install chardet
      import chardet
      with open('文件名样例.txt', 'rb') as f:
          raw = f.read()
          result = chardet.detect(raw)
          print(result['encoding'])  # 但 chardet 对文件名效果有限
  2. 安全第一: 无论是 convmv 还是 --dry-run永远先预览再执行

  3. 递归与目录名: 如果目录名也是乱码,先处理目录内的文件,再处理目录名(Python 脚本已处理好这个顺序)。

  4. 特殊字符: 文件名中可能有 、、 等非法字符,如果解码后出现这些字符,需要额外替换:

    # 在 decode_filename 函数中添加
    new_name = new_name.replace(':', '-').replace('/', '-').replace('\n', '')

对于 Linux/macOS,首选 convmv;对于跨平台或复杂场景,使用 Python 脚本;Windows 用户可以使用 PowerShell 脚本。

抱歉,评论功能暂时关闭!