文件转UTF8
有时候项目的文件是其他编码格式,在不同的编辑器下显示乱码,现在统一为UTF8,下面是python代码
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import chardet # 文本文件扩展名白名单 TEXT_EXTENSIONS = { '.c', '.h', '.cpp', '.hpp', '.cc', '.cxx', '.hxx', # C/C++ '.py', '.pyw', # Python '.java', # Java '.cs', # C# '.go', # Go '.rs', # Rust '.js', '.jsx', '.ts', '.tsx', # JavaScript/TypeScript '.html', '.htm', '.css', '.scss', '.sass', '.less', # 前端 '.xml', '.json', '.yaml', '.yml', '.toml', # 配置文件 '.txt', '.log', '.md', '.markdown', # 文档 '.sql', # SQL '.sh', '.bash', '.zsh', # Shell '.bat', '.cmd', '.ps1', # Windows脚本 '.ini', '.cfg', '.conf', '.config', # 配置文件 '.cmake', '.make', '.mk', # Makefile '.sln', '.vcxproj', '.filters', # VS项目文件 '.lua', '.rb', '.php', '.pl', '.pm', # 其他语言 } def is_text_file(file_path): """ 判断是否为文本文件 先检查扩展名,如果不在白名单中,通过检测内容是否包含空字节来判断 """ # 1. 检查扩展名 ext = os.path.splitext(file_path)[1].lower() if ext in TEXT_EXTENSIONS: return True # 2. 扩展名不在白名单中,检查内容是否包含空字节(二进制特征) try: with open(file_path, 'rb') as f: chunk = f.read(1024) # 空文件视为文本文件 if not chunk: return True # 如果包含空字节,很可能是二进制文件 if b'\x00' in chunk: return False # 没有空字节,使用chardet检测 detected = chardet.detect(chunk) if detected.get('encoding') is None: return False # 常见的文本编码 text_encodings = {'utf-8', 'ascii', 'gbk', 'gb2312', 'gb18030', 'big5', 'shift-jis', 'euc-kr', 'utf-16', 'cp936'} return detected.get('encoding', '').lower() in text_encodings except Exception: return False def convert_to_utf8_bom(file_path, verbose=True): """ 将文件转换为带BOM的UTF-8编码(Visual Studio可正确识别) Args: file_path: 文件的绝对路径 verbose: 是否输出详细信息 Returns: bool: 转换是否成功 """ # 检查文件是否存在 if not os.path.exists(file_path): if verbose: print(f"❌ 错误:文件不存在 - {file_path}") return False # 检查是否为文件(而非目录) if not os.path.isfile(file_path): if verbose: print(f"❌ 错误:路径指向的是目录,不是文件 - {file_path}") return False try: # 读取原始文件的二进制内容 with open(file_path, 'rb') as f: raw_data = f.read() # 空文件直接跳过 if not raw_data: return True # 检查是否已经是带BOM的UTF-8 if raw_data.startswith(b'\xef\xbb\xbf'): if verbose: print(f"⏭️ 跳过(已是UTF-8-BOM): {os.path.basename(file_path)}") return True # 使用chardet检测文件编码 detected = chardet.detect(raw_data) original_encoding = detected.get('encoding', 'unknown') confidence = detected.get('confidence', 0) # 格式化置信度为百分比 confidence_percent = confidence * 100 # 显示检测信息 if verbose: encoding_display = original_encoding.upper() if original_encoding else 'UNKNOWN' print(f"🔍 检测到编码: {encoding_display} (置信度: {confidence_percent:.2f}%)") # 尝试解码 content = None decoded_encoding = None # 优先使用chardet检测到的编码 if original_encoding and original_encoding.lower() != 'unknown': try: content = raw_data.decode(original_encoding) decoded_encoding = original_encoding except (UnicodeDecodeError, TypeError): if verbose: print(f"⚠️ 使用 {original_encoding} 解码失败,尝试其他编码...") # 如果chardet解码失败,尝试常见编码 if content is None: for enc in ['gbk', 'gb2312', 'gb18030', 'utf-8', 'big5', 'cp936', 'latin-1', 'windows-1252']: if enc == original_encoding: continue try: content = raw_data.decode(enc) decoded_encoding = enc if verbose: print(f"✅ 使用 {enc.upper()} 编码解码成功") break except UnicodeDecodeError: continue # 如果所有编码都失败 if content is None: if verbose: print(f"❌ 错误:无法解码文件内容 - {os.path.basename(file_path)}") return False # 写回文件:UTF-8 with BOM with open(file_path, 'wb') as f: # 先写入BOM头 (EF BB BF) f.write(b'\xef\xbb\xbf') # 再写入UTF-8编码的内容 f.write(content.encode('utf-8')) if verbose: original_display = decoded_encoding.upper() if decoded_encoding else 'UNKNOWN' print(f"✅ 转换成功:{file_path}") print(f" 原始编码: {original_display} → 目标编码: UTF-8 with BOM") print() return True except PermissionError: if verbose: print(f"❌ 错误:没有权限访问文件 - {file_path}") return False except Exception as e: if verbose: print(f"❌ 转换失败: {str(e)}") return False def process_path(path, verbose=True, dry_run=False): """ 处理文件或目录 Args: path: 文件或目录的绝对路径 verbose: 是否输出详细信息 dry_run: 是否仅预览(不实际转换) Returns: tuple: (成功数, 失败数, 跳过数) """ if not os.path.exists(path): print(f"❌ 路径不存在: {path}") return (0, 0, 0) # 如果是文件,直接处理 if os.path.isfile(path): if not is_text_file(path): print(f"⏭️ 跳过(非文本文件): {os.path.basename(path)}") return (0, 0, 1) if dry_run: print(f"🔍 [预览] 将转换: {path}") return (1, 0, 0) success = convert_to_utf8_bom(path, verbose) return (1 if success else 0, 0 if success else 1, 0) # 如果是目录,递归处理 if os.path.isdir(path): success_count = 0 fail_count = 0 skip_count = 0 print(f"📂 扫描目录: {path}") print("=" * 70) for root, dirs, files in os.walk(path): # 跳过常见的版本控制目录和编译输出目录 skip_dirs = { '.git', '.svn', '.hg', 'node_modules', '__pycache__', 'bin', 'obj', 'Debug', 'Release', '.vs', 'build', 'dist', 'venv', 'env', '.venv', 'vendor', 'x64', 'x86', 'Win32', 'lib', 'dll', 'out' } dirs[:] = [d for d in dirs if d not in skip_dirs] for file in files: file_path = os.path.join(root, file) # 跳过隐藏文件(Unix风格) if file.startswith('.'): continue # 跳过常见的二进制文件扩展名 binary_ext = { '.exe', '.dll', '.so', '.dylib', '.lib', '.a', '.obj', '.o', '.pdb', '.pch', '.ilk', '.exp', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.mp3', '.mp4', '.avi', '.mkv', '.flv', '.wmv', '.zip', '.tar', '.gz', '.bz2', '.7z', '.rar', '.pdf', '.doc', '.xls', '.ppt', '.docx', '.xlsx', '.pptx', '.vsdx' } if os.path.splitext(file)[1].lower() in binary_ext: skip_count += 1 if verbose: print(f"⏭️ 跳过(二进制): {file}") continue # 判断是否为文本文件 if not is_text_file(file_path): skip_count += 1 if verbose: print(f"⏭️ 跳过(非文本): {file}") continue if dry_run: print(f"🔍 [预览] 将转换: {file_path}") success_count += 1 continue if convert_to_utf8_bom(file_path, verbose): success_count += 1 else: fail_count += 1 print("=" * 70) print(f"📊 统计: 成功 {success_count} | 失败 {fail_count} | 跳过 {skip_count}") return (success_count, fail_count, skip_count) print(f"❌ 未知路径类型: {path}") return (0, 0, 0) def main(): """主函数:处理命令行参数""" # 检查参数数量 if len(sys.argv) < 2: print("=" * 70) print("文件编码转换工具 - 转UTF-8-BOM (Visual Studio兼容)") print("=" * 70) print("\n使用方法:") print(" python convert_to_utf8_bom.py <文件或文件夹路径> [选项]") print("\n选项:") print(" -v, --verbose 显示详细输出(默认启用)") print(" -q, --quiet 静默模式,不显示详细信息") print(" -d, --dry-run 仅预览,不实际转换") print(" -h, --help 显示帮助信息") print("\n示例:") print(" python convert_to_utf8_bom.py D:\\Project\\main.h") print(" python convert_to_utf8_bom.py D:\\Project\\src") print(" python convert_to_utf8_bom.py D:\\Project\\src -q") print(" python convert_to_utf8_bom.py D:\\Project\\src -d") print("=" * 70) sys.exit(1) # 解析参数 path = sys.argv[1] verbose = True # 默认显示详细信息 dry_run = False # 处理选项参数 for arg in sys.argv[2:]: if arg in ['-q', '--quiet']: verbose = False elif arg in ['-d', '--dry-run']: dry_run = True elif arg in ['-v', '--verbose']: verbose = True elif arg in ['-h', '--help']: print("帮助信息...") sys.exit(0) # 执行处理 process_path(path, verbose=verbose, dry_run=dry_run) if __name__ == "__main__": main()
使用很简单 例如保存为a.py 那么就这样使用
python d:/test.txt
python d:/testdir
浙公网安备 33010602011771号