首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

Python 批量轉換檔案編碼:GBK 轉 UTF-8 跨平台腳本(支援 Python 2/3)

     編輯:更多程式設計知識

專案遷移、舊系統整合時常需將大量原始碼、設定檔、文檔從 GBK/Big5 等舊編碼統一轉為 UTF-8。手動逐檔轉換不現實,本文提供三個版本的 Python 腳本,分別針對 Linux(呼叫 iconv)、Python 2、Python 3 純標準庫實現,支援遞迴目錄、跳過二進位檔、錯誤處理與乾淨輸出。

版本選擇指南

環境推薦版本特點
Linux/macOS 且裝有 iconv版本 A(呼叫 iconv)速度最快、支援最多編碼、自動處理 BOM
純 Python 2 環境版本 B無外部依賴、相容舊系統
Python 3.6+版本 C(推薦)標準庫 codecs/open、錯誤處理完善、跨平台

版本 A:Linux 專用,呼叫 iconv(最快)


#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os, sys, subprocess

TARGET_EXTS = ('.py', '.txt', '.md', '.html', '.css', '.js', '.json', '.xml', '.yml', '.yaml', '.ini', '.cfg', '.conf', '.sh', '.sql')
SKIP_DIRS = ('.git', '.svn', '__pycache__', 'node_modules', 'venv', 'env', 'dist', 'build')

def is_text_file(path):
    """簡易判斷:嘗試以 utf-8 讀取前 1024 位元組,失敗再嘗試 gbk"""
    try:
        with open(path, 'rb') as f:
            chunk = f.read(1024)
        if b'\x00' in chunk:  # 含 null byte 視為二進位
            return False
        chunk.decode('utf-8')
        return True
    except UnicodeDecodeError:
        try:
            chunk.decode('gbk')
            return True
        except UnicodeDecodeError:
            return False

def convert_file(path):
    # 使用 iconv -f gbk -t utf-8 -c(忽略無效字元) -o 輸出
    # 先備份再覆寫,避免 iconv 失敗損壞原檔
    backup = path + '.bak'
    os.rename(path, backup)
    cmd = ['iconv', '-f', 'gbk', '-t', 'utf-8', '-c', '-o', path, backup]
    ret = subprocess.call(cmd)
    if ret == 0:
        os.remove(backup)
        print(f"✅ {path}")
    else:
        os.rename(backup, path)
        print(f"❌ {path} 轉換失敗,已還原")

def main(root_dir):
    for dirpath, dirnames, filenames in os.walk(root_dir):
        # 就地修改 dirnames 以跳過目錄
        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
        for fname in filenames:
            if fname.lower().endswith(TARGET_EXTS):
                full = os.path.join(dirpath, fname)
                if is_text_file(full):
                    convert_file(full)

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else '.'
    main(os.path.abspath(target))

版本 B:Python 2 純標準庫


#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os, sys, codecs

def convert(path, in_enc='GBK', out_enc='UTF-8'):
    try:
        with open(path, 'rb') as f:
            content = f.read()
        # 解碼 -> 編碼
        new_content = content.decode(in_enc).encode(out_enc)
        with open(path, 'wb') as f:
            f.write(new_content)
        print(f"✅ {path}")
    except Exception as e:
        print(f"❌ {path}: {e}")

def walk(root):
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if not d.startswith('.')]
        for fname in filenames:
            if fname.lower().endswith(('.py', '.txt', '.md', '.html', '.js', '.css', '.json', '.xml')):
                convert(os.path.join(dirpath, fname))

if __name__ == "__main__":
    for p in sys.argv[1:] or ['.']:
        if os.path.isfile(p):
            convert(p)
        elif os.path.isdir(p):
            walk(p)

版本 C:Python 3 現代寫法(推薦)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, sys, codecs, argparse
from pathlib import Path

DEFAULT_EXTS = {'.py', '.txt', '.md', '.html', '.htm', '.css', '.js', '.json', '.xml', '.yml', '.yaml', '.ini', '.cfg', '.conf', '.sh', '.sql', '.csv', '.log'}
SKIP_DIRS = {'.git', '.svn', '__pycache__', 'node_modules', 'venv', 'env', 'dist', 'build', '.idea', '.vscode'}

def detect_encoding(path, sample_size=4096):
    """嘗試常見編碼,回傳 (encoding, confidence)"""
    with open(path, 'rb') as f:
        raw = f.read(sample_size)
    if b'\x00' in raw:
        return None  # 二進位
    for enc in ('utf-8', 'utf-8-sig', 'gbk', 'gb18030', 'big5', 'latin-1'):
        try:
            raw.decode(enc)
            return enc
        except UnicodeDecodeError:
            continue
    return None

def convert_file(path, target_enc='utf-8', dry_run=False):
    src_enc = detect_encoding(path)
    if not src_enc:
        print(f"⏭ 跳過(二進位/無法識別): {path}")
        return False
    if src_enc.lower().replace('-', '') == target_enc.lower().replace('-', ''):
        print(f"⏭ 已是 {target_enc}: {path}")
        return True
    try:
        # 讀取完整內容(若檔案極大可改分塊,但文字檔通常較小)
        with open(path, 'r', encoding=src_enc) as f:
            content = f.read()
        if not dry_run:
            # 寫入 UTF-8(不加 BOM)
            with open(path, 'w', encoding=target_enc, newline='') as f:
                f.write(content)
        print(f"✅ {path} ({src_enc} → {target_enc})")
        return True
    except Exception as e:
        print(f"❌ {path}: {e}")
        return False

def main():
    parser = argparse.ArgumentParser(description="批量將文字檔轉為 UTF-8 編碼")
    parser.add_argument('paths', nargs='*', default=['.'], help='檔案或目錄路徑')
    parser.add_argument('-e', '--ext', nargs='*', help='額外副檔名(如 .vue .tsx)')
    parser.add_argument('--dry-run', action='store_true', help='僅模擬,不寫入')
    parser.add_argument('--target', default='utf-8', help='目標編碼(預設 utf-8)')
    args = parser.parse_args()

    exts = DEFAULT_EXTS | set(args.ext) if args.ext else DEFAULT_EXTS

    total = converted = 0
    for p in args.paths:
        path = Path(p).resolve()
        if path.is_file():
            if path.suffix.lower() in exts:
                total += 1
                if convert_file(path, args.target, args.dry_run):
                    converted += 1
        elif path.is_dir():
            for file in path.rglob('*'):
                if file.is_file() and file.suffix.lower() in exts:
                    # 跳過 SKIP_DIRS
                    if any(part in SKIP_DIRS for part in file.parts):
                        continue
                    total += 1
                    if convert_file(file, args.target, args.dry_run):
                        converted += 1
    print(f"\n完成:共 {total} 個候選檔案,成功轉換 {converted} 個。")

if __name__ == "__main__":
    main()

使用範例


# Python 3 版:預設遞迴當前目錄,支援乾跑模式
python3 convert_encoding.py -e .vue .tsx --dry-run
python3 convert_encoding.py /path/to/project

# Python 2 版
python2 convert_py2.py /path/to/project

最佳實踐提醒

  1. 先備份、再轉換:版本 A 會自動備份 `.bak`;版本 B/C 建議先 `git commit` 或 `cp -r` 整個專案。
  2. 版本控管:轉換後立即 `git diff` 檢查異常字元(如亂碼變 ``),再提交。
  3. 編碼偵測不準:短檔案易誤判,可加大 `sample_size` 或強制指定來源編碼。
  4. BOM 處理:Python 3 `utf-8-sig` 讀取會自動移除 BOM,寫入 `utf-8` 不加 BOM,符合現代規範。

選對版本、做好備份、善用版控,批量編碼轉換就能安全、高效完成。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved