Python实现按数字命名合并文本文件
本文介绍了一个基于Tkinter的文本文件合并工具GUI应用。该工具主要功能包括:
1)支持选择源文件夹和输出文件路径;
2)可按文件扩展名(.txt/.csv等)过滤文件;
3)自动识别文件名中的数字前缀进行排序合并;
4)提供日志记录和状态显示。
核心实现包括文件遍历、数字前缀提取、内容标准化处理等功能,GUI界面包含目录选择、文件过滤、日志显示等模块。该工具适合需要按文件名顺序合并多个文本文件的场景,支持UTF-8编码处理,并能统一换行符格式。使用Python标准库实现,无需额外依赖。
import os
import re
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter.scrolledtext import ScrolledTextclass FileMergerApp:def __init__(self, root):self.root = rootself.root.title("文本文件合并工具")self.root.geometry("800x600")self.center_window() # 居中窗口self.root.resizable(True, True)# 设置样式self.style = ttk.Style()self.style.configure("TButton", padding=6, font=("Arial", 10))self.style.configure("TLabel", font=("Arial", 10))self.style.configure("TEntry", font=("Arial", 10))# 创建主框架main_frame = ttk.Frame(root, padding=20)main_frame.pack(fill=tk.BOTH, expand=True)# 输入目录选择input_frame = ttk.LabelFrame(main_frame, text="源文件夹", padding=10)input_frame.pack(fill=tk.X, pady=(0, 10))self.input_dir = tk.StringVar()ttk.Entry(input_frame, textvariable=self.input_dir, width=50).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))ttk.Button(input_frame, text="浏览...", command=self.select_input_dir).pack(side=tk.RIGHT)# 输出文件选择output_frame = ttk.LabelFrame(main_frame, text="输出文件", padding=10)output_frame.pack(fill=tk.X, pady=(0, 10))self.output_file = tk.StringVar()ttk.Entry(output_frame, textvariable=self.output_file, width=50).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))ttk.Button(output_frame, text="浏览...", command=self.select_output_file).pack(side=tk.RIGHT)# 文件类型过滤filter_frame = ttk.LabelFrame(main_frame, text="文件类型过滤", padding=10)filter_frame.pack(fill=tk.X, pady=(0, 10))self.file_types = tk.StringVar(value=".txt;.csv;.log;.md;.json")ttk.Label(filter_frame, text="支持的扩展名 (用分号分隔):").pack(side=tk.LEFT, padx=(0, 10))ttk.Entry(filter_frame, textvariable=self.file_types, width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)# 日志区域log_frame = ttk.LabelFrame(main_frame, text="操作日志", padding=10)log_frame.pack(fill=tk.BOTH, expand=True)self.log_area = ScrolledText(log_frame, height=15, wrap=tk.WORD)self.log_area.pack(fill=tk.BOTH, expand=True)self.log_area.config(state=tk.DISABLED)# 按钮区域button_frame = ttk.Frame(main_frame, padding=(0, 10))button_frame.pack(fill=tk.X)ttk.Button(button_frame, text="合并文件", command=self.merge_files).pack(side=tk.RIGHT, padx=(10, 0))ttk.Button(button_frame, text="清除日志", command=self.clear_log).pack(side=tk.RIGHT)ttk.Button(button_frame, text="退出", command=root.quit).pack(side=tk.LEFT)# 状态栏self.status_var = tk.StringVar(value="就绪")status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)status_bar.pack(side=tk.BOTTOM, fill=tk.X)def center_window(self):"""将窗口居中显示在屏幕上"""# 更新窗口,确保获取正确的尺寸self.root.update_idletasks()# 获取屏幕尺寸screen_width = self.root.winfo_screenwidth()screen_height = self.root.winfo_screenheight()# 获取窗口尺寸window_width = self.root.winfo_width()window_height = self.root.winfo_height()# 如果窗口尺寸为1,说明尚未渲染,使用初始设置尺寸if window_width == 1 or window_height == 1:window_width = 800window_height = 600# 计算居中位置x = (screen_width - window_width) // 2y = (screen_height - window_height) // 2# 设置窗口位置self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")def log_message(self, message):"""在日志区域添加消息"""self.log_area.config(state=tk.NORMAL)self.log_area.insert(tk.END, message + "\n")self.log_area.config(state=tk.DISABLED)self.log_area.see(tk.END) # 滚动到底部self.root.update_idletasks() # 更新界面def clear_log(self):"""清除日志内容"""self.log_area.config(state=tk.NORMAL)self.log_area.delete(1.0, tk.END)self.log_area.config(state=tk.DISABLED)self.status_var.set("日志已清除")def select_input_dir(self):"""选择输入目录"""directory = filedialog.askdirectory(title="选择源文件夹")if directory:self.input_dir.set(directory)# 自动设置默认输出文件名if not self.output_file.get():default_output = os.path.join(directory, "合并结果.txt")self.output_file.set(default_output)def select_output_file(self):"""选择输出文件"""file_path = filedialog.asksaveasfilename(title="保存合并文件",filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],defaultextension=".txt")if file_path:self.output_file.set(file_path)def extract_numeric_prefix(self, filename):"""从文件名中提取数字前缀和剩余部分:param filename: 文件名:return: (数字值, 剩余部分) 或 (None, None) 如果没有数字前缀"""# 匹配数字前缀(可能包含空格、括号等分隔符)match = re.match(r'^(\d+)[\s\-_()]*(.*)', filename)if match:number_part = match.group(1)remaining = match.group(2)return int(number_part), remainingreturn None, None # 返回None表示没有数字前缀def normalize_line_endings(self, content):"""统一换行符为LF,并确保末尾只有一个换行符"""# 替换所有换行符为LFcontent = content.replace('\r\n', '\n').replace('\r', '\n')# 去除末尾多余的换行符content = content.rstrip('\n')# 添加单个换行符return content + '\n'def merge_files(self):"""执行文件合并操作"""input_dir = self.input_dir.get()output_file = self.output_file.get()if not input_dir or not os.path.isdir(input_dir):messagebox.showerror("错误", "请选择有效的源文件夹")returnif not output_file:messagebox.showerror("错误", "请指定输出文件路径")return# 获取文件类型过滤extensions = [ext.strip().lower() for ext in self.file_types.get().split(';') if ext.strip()]try:# 获取文件夹中所有文件列表all_files = [f for f in os.listdir(input_dir)if os.path.isfile(os.path.join(input_dir, f))]# 处理文件并提取排序键to_merge = [] # 待合并的文件列表non_numeric_files = [] # 非数字开头的文件列表for filename in all_files:# 检查文件扩展名if extensions and not any(filename.lower().endswith(ext) for ext in extensions):continue# 提取数字前缀和剩余部分num_prefix, remaining = self.extract_numeric_prefix(filename)if num_prefix is not None:# 创建排序元组:(数字前缀, 剩余部分, 文件名)to_merge.append((num_prefix, remaining, filename))else:non_numeric_files.append(filename)# 按数字前缀排序,相同数字前缀时按剩余部分排序sorted_files = sorted(to_merge, key=lambda x: (x[0], x[1]))# 记录非数字开头的文件if non_numeric_files:self.log_message("以下文件不以数字开头,不参与合并:")for filename in non_numeric_files:self.log_message(f" - {filename}")self.log_message(f"共跳过 {len(non_numeric_files)} 个非数字开头的文件\n")if not sorted_files:messagebox.showinfo("提示", "没有找到符合要求的数字开头的文件")return# 创建输出文件的目录(如果不存在)output_dir = os.path.dirname(output_file)if output_dir and not os.path.exists(output_dir):os.makedirs(output_dir)self.log_message(f"开始合并 {len(sorted_files)} 个数字开头的文件...")self.status_var.set(f"正在合并 {len(sorted_files)} 个文件...")self.root.update() # 更新UI# 按顺序合并文件with open(output_file, 'w', encoding='utf-8') as outfile:for i, (num, rem, filename) in enumerate(sorted_files, 1):file_path = os.path.join(input_dir, filename)try:with open(file_path, 'r', encoding='utf-8') as infile:# 读取文件内容content = infile.read()# 标准化换行符并确保末尾只有一个换行符content = self.normalize_line_endings(content)# 写入文件内容(不带任何额外信息)outfile.write(content)log_msg = f"已合并文件 #{i}: {filename} (数字前缀: {num})"self.log_message(log_msg)self.root.update() # 更新UIexcept UnicodeDecodeError:self.log_message(f"跳过非文本文件: {filename}")except Exception as e:self.log_message(f"处理文件 {filename} 时出错: {str(e)}")self.log_message(f"\n合并完成!输出文件: {os.path.abspath(output_file)}")self.status_var.set(f"合并完成!共合并 {len(sorted_files)} 个文件")message = f"文件合并完成!\n共合并 {len(sorted_files)} 个文件。"if non_numeric_files:message += f"\n跳过 {len(non_numeric_files)} 个非数字开头的文件。"message += f"\n输出文件: {output_file}"messagebox.showinfo("完成", message)except Exception as e:self.log_message(f"发生错误: {str(e)}")messagebox.showerror("错误", f"合并过程中发生错误:\n{str(e)}")self.status_var.set("错误: " + str(e))if __name__ == "__main__":root = tk.Tk()app = FileMergerApp(root)root.mainloop()