os模块,os.path模块,subprocess模块,configparser模块,shutil模块

本文详细介绍了Python中处理文件和配置文件的方法,包括os模块、os.path模块、subprocess模块、configparser模块和shutil模块的功能和使用。涵盖文件操作、路径处理、子进程启动、配置文件读写和文件复制压缩等实用技能。
1.os模块

os表示操作系统
该模块主要用来处理与操作系统相关的操作
最常用的文件操作
打开 读入 写入 删除 复制 重命名

os.getcwd() 获取当前执行文件所在的文件夹路径
os.chdir("dirname") 改变当前脚本工作目录
os.curdir 返回当前目录的字符串表现方式: 结果('.')
os.pardir 获取当前目录的父目录字符串名:结果('..')
os.makedirs('a/b/c') 可生成多层递归目录,如果不存在,可以自动创建中间的目录
os.mkdir('a/b/c') 生成单级目录,会把a,b当成已存在的路径,如果不存在就会报错
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;
os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印,\
列表里面只是文件的名称.需要手动拼接其完整路径
os.remove() 删除一个文件
# 如果要删除一个文件夹 而且文件夹不为空 ,思路是 先遍历这个文件夹中的所有文件和文件夹
# 先把里面的内容全部删除 最后再删除文件夹
有一个文件夹a,里面包含了一个a.txt的文件
path=r'E:\python-li\课堂\day19\a'
for f in os.listdir('a'):#得到的f只是字符串类型的文件名
  f_path=path+'\\'+'a.txt'#将其改为文件路径的格式
  print(f_path)
  os.remove(f_path)
os.rmdir('a')

os.rename("oldname","newname") 重命名文件/目录
os.stat('path/filename') 获取文件/目录信息
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/",当你的程序需要跨平台时,路径分隔符不能直接写死要从os中获取
os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:
os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command") 运行shell命令(系统命令),直接显示它在执行系统指令时,也可以命令操作系统启动某个程序
os.environ 获取系统环境变量

什么时候使用os 当需要操作文件及文件夹的时候
重点放在对文件的增删改查

2.os.path模块

path翻译为路径
该模块用于处理路径
之所以用这个模块是因为python的跨平台,各平台的路径书写方式不同,所以将所有与路径相关都进行了封装

os.path.abspath(path) 返回path规范化的绝对路径,其实就是把当前文件所在的文件夹与你给的参数进行了拼接
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True 依据:第一个字符是不是路径分割符合,是表示绝对路径
print(os.path.isabs(r'E:\python-li\课堂\day19')) 结果:True
print(os.path.isabs(r'python-li\课堂\day19')) 结果:False
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的文件夹,则返回True。否则返回False
os.path.join(path1,path2) 以当前平台的路径分隔符来进行拼接.如果有多个盘符,取得是最后一个
print(os.path.join('D:\\','a')) 结果:D:\a
print(os.path.join('D:\\','E:\\','a')) 结果:E:\a
os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小

#返回规范化路径 大写变成小写,反斜杠变成正斜杠
print(os.path.normcase(r'E:\python-li\课堂/day19\os模块.py'))
结果:e:\python-li\课堂\day19\os模块.py

# 规范化路径但大写不变小写 并且 会执行里面..(用于返回上一级目录)
print(os.path.normpath(r'E:\python-li\\课堂//day19\os模块.py\..'))
结果:E:\python-li\课堂\day19
print(os.path.normpath(r'E:\python-li\\课堂\day19\os模块.py\..\..'))
结果:E:\python-li\课堂

#获取项目根目录
import sys,os
sys.path.append(os.path.dirname(os.path.dirname(__file__))

总结:主要处理路径 不会关系路径是否存在 只是做拼接 剪切 转换等等操作
通常是与os一起使用
优点: 用它处理的路径是可以跨平台的

3.subprocess模块

# '''
# subprocess 翻译为子进程
# 进程指的是正在运行的程序
# 子进程 是有另一个正在运行的程序启动的程序 例如:qq聊天 点击了一个连接 打开了浏览器 那么浏览器称之为qq的子进程
# 为什么使用子进程?
# 当我们有一个任务需要处理 而自己的程序无法处理 所以需要开启另一个程序
# 在python中想要获取所有的进程(任务列表)信息
# '''
# import os
# os.system(r"F:\QQ2016\Bin\QQScLauncher.exe")
# os.system('dir')
#它在执行系统指令时,也可以命令操作系统启动某个程序
#os.system在执行时 直接把结果输出到了控制台 如果我们要获取执行的结果就无能为力了
#subprocess不仅可以启动子进程,还能与子进程进行数据交互

import subprocess
#dir 表示要执行命令
#shell 表示dir是一个命令
#stout 指定输出的管道
#管道是什么? 相当于生活中的水管 从一个地方流到另一个地方
#在程序中,数据相当于水 管道的作用,就从一个进程中把数据输到另一个进程
#本质是读写同一个文件
# p = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE)
# print(p.stdout.read())
#读一次指针到达文件的末尾,再次读就没有数据

#启动一个tasklist子进程,指定输出结果到管道中
p1=subprocess.Popen('tasklist',shell=True,stdout=subprocess.PIPE)
#启动一个findstr的子程序,将p1进程的结果作为p2进程输入
p2=subprocess.Popen('findstr cmd', #要执行的指令
          shell=True, #第一个参数是否是一个指令
          stdin=p1.stdout, #指定输入管道
          stdout=subprocess.PIPE, #指定输出管道
          stderr=subprocess.PIPE)#表示错误管道,当进程执行出错时,可以在错误管道中获取结果
#读取p2进程的结果
print(p2.stdout.read())
print(p2.stderr.read().decode('GBK'))


#总结:当你需要在python中启动一个子进程,并且它进行数据交互时就是用subprocess
#如果不需要数据交互可以使用 os.system

4.configparser模块

configparser,翻译为配置解析,很显然,它是用来解析配置文件的
何为配置文件?
用于编写程序的配置信息的文件
何为配置信息?
为了提高程序的扩展性,我们会把一些程序中需要用到的值交给用户来确定,比如迅雷的下载目录,同时下载数,qq的提示音等.
作为配置信息的数据,应满足两个条件
1.数据的值不是固定的
2.可以由用户来指定
例如:我们做一个登录功能,为了方便使用我们可以将用户的用户名密码写到配置文件中,可以不需要内次都输入

配置文件编写格式
配置文件中只允许出现两种类型的数据
第一种 section分区 方括号中十分区的名称 例如[ATM]
第二种 option选项 名称=值
注意:
不能出现重复的分区名
同一分区下不能有相同的选项名

在test.cfg中:
#这是一个section(分区)
[mysql]
#这是一个option(选项)
username = henry
password = 123
lock = false
#注意分区名不能重复
#同一个分区下 不能有重复option
[django]

在configparser模块中:
import configparser
#创建一个配置文件解析器
cfg = configparser.ConfigParser()
#读取名为test.cfg的配置文件
cfg.read('test.cfg',encoding='utf-8')
#获取分区
print(cfg.sections())
#获取某个分区下的某个选项,第一个参数分区名 第二个选项名
username=cfg.get('mysql','username')
print(username)
print(type(username))

password=cfg.get('mysql','password')
print(password)
print(type(password))

lock=cfg.getboolean('mysql','lock')
print(type(lock))
print(lock)

#以下三个类型
cfg.getfloat()
cfg.getint()
cfg.getboolean()

#读取配置信息 两步
1.读取某个配置文件
2.调用get函数

configparser的增加修改删除

#修改
import configparser
cfg=configparser.ConfigParser()
cfg.read('test.cfg',encoding='utf-8')
#将mysql分区下的lock值改为True
cfg.set('mysql','lock','true')
with open('test.cfg','wt',encoding='utf-8') as f:
cfg.write(f)

#增加
import configparser
cfg=configparser.ConfigParser()
cfg.read('test.cfg',encoding='utf-8')
#增加新分区
cfg.add_section('新分区')
#增加新选项port 值为 3306
cfg.set('mysql','port','3306')
with open('test.cfg','wt',encoding='utf-8') as f:
cfg.write(f)

#删除
import configparser
cfg=configparser.ConfigParser()
cfg.read('test.cfg',encoding='utf-8')
#删除分区
cfg.remove_section('新分区')
#删除某分区的选项
cfg.remove_option('mysql','port')
with open('test.cfg','wt',encoding='utf-8') as f :
cfg.write(f)

#判断是否存在某个分区
print(cfg.has_section('mysql'))
#判断是否存在某个选项
print(cfg.has_option('mysql','username'))

#作为配置文件 最常用的操作就是读取配置信息 很少会做修改
#总结:
read读取配置文件注意要写encoding
add_section添加新分区
set 如果没有这个选项则添加,有则修改
remove_section 删除分区
remove_option 删除选项

5.shutil模块

shutil模块是一个工具包,封装了文件高级操作,让你操作更方便

功能与os有些重叠,os只能帮你处理文件是否存在,路径是否正确等,无法直接完成copy等操作,而shutil提供了压缩与解压缩
import shutil
#copy文件
shutil.copyfile(r'E:\python-li\课堂\day19\os模块.py',\
      r'E:\python-li\课堂\day19\os模块2.py')
#压缩文件 支持的格式zip 和tar
shutil.make_archive('day19','zip',r'E:\python-li\课堂')
shutil.make_archive('os.path','tar',r'E:\python-li\课堂\day19')
#解压缩
shutil.unpack_archive(r'E:\python-li\课堂\day19\day19.zip',
          r'E:\python-li\课堂\day19\day19\解压的文件夹.zip'
          r'zip')
总结:拷贝文件,压缩解压缩文件
make_archive 中的第四个参数 base_dir 也是用来指定要压缩的路径,与root_dir的区别
#root_dir 仅仅压缩指定的路径下的内容
#base_dir 会将指定的路径下的内容 与其完整的文件层级一并压缩
#当指定base_dir时root_dir不生效

转载于:https://www.cnblogs.com/lizeqian1994/p/10104484.html

import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import threading import queue import traceback import webbrowser import datetime import configparser class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高级文件夹比较工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # 创建现代风格主题 self.style = ttk.Style() self.style.theme_use('clam') # 自定义主题颜色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # 创建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加标题图标 try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高级文件夹比较工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件夹选择 - 使用独立的配置键 self.old_folder_entry, _ = self.create_folder_selector(file_frame, "原始文件夹:", "old_folder") self.new_folder_entry, _ = self.create_folder_selector(file_frame, "修改后文件夹:", "new_folder") # 比较选项区域 options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) # 递归比较选项 self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件过滤 filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 输出设置区域 self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) # 目标Excel选择 ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, "excel_file", [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # WinMerge路径设置 winmerge_frame = ttk.Frame(self.excel_frame) winmerge_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(winmerge_frame, text="WinMerge路径:").grid(row=0, column=0, sticky=tk.W) self.winmerge_entry = ttk.Entry(winmerge_frame, width=60) self.winmerge_entry.grid(row=0, column=1, padx=5) self.winmerge_entry.insert(0, r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe") # 默认路径 ttk.Button(winmerge_frame, text="浏览...", command=lambda: self.select_file(self.winmerge_entry, "winmerge_path", [("WinMerge 可执行文件", "*.exe")])).grid(row=0, column=2) # 执行按钮区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按钮 self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # 查看文件夹报告按钮 self.view_folder_report_button = ttk.Button(button_frame, text="查看文件夹报告", command=lambda: self.view_report("folder"), width=15, state=tk.DISABLED) self.view_folder_report_button.pack(side=tk.LEFT, padx=10) # 进度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状态信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和预览区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件夹结构标签 tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") # 创建树形视图 self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志标签 log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # 设置网格权重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # 线程控制 self.processing = False self.queue = queue.Queue() self.folder_report_path = None self.files_dir = None # 配置文件路径 self.config_file = "folder_compare_config.ini" # 加载保存的路径 self.load_paths() # 启动队列处理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text, config_key): """创建文件夹选择器组件并绑定保存功能""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry, config_key)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry, config_key): """选择文件夹并保存路径,同时设置文件对话框的初始目录""" # 获取上次保存的路径作为初始目录 initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() foldername = filedialog.askdirectory(initialdir=initial_dir) if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) self.populate_folder_tree(foldername) self.save_path(config_key, foldername) def select_file(self, entry, config_key, filetypes=None): """选择文件并保存路径,同时设置文件对话框的初始目录""" if filetypes is None: filetypes = [("所有文件", "*.*")] # 获取上次保存的路径作为初始目录 initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() # 如果路径是文件,则使用其父目录 if os.path.isfile(initial_dir): initial_dir = os.path.dirname(initial_dir) filename = filedialog.askopenfilename(filetypes=filetypes, initialdir=initial_dir) if filename: entry.delete(0, tk.END) entry.insert(0, filename) self.save_path(config_key, filename) def get_last_path(self, config_key): """获取上次保存的路径""" config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if config.has_option('Paths', config_key): return config.get('Paths', config_key) return None def populate_folder_tree(self, path): """填充文件夹结构树""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """递归添加树节点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): """记录日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新进度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状态信息""" self.queue.put(("status", message)) def process_queue(self): """处理线程队列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def view_report(self, report_type): """查看生成的报告""" if report_type == "folder" and self.folder_report_path and os.path.exists(self.folder_report_path): try: webbrowser.open(self.folder_report_path) except Exception as e: messagebox.showerror("错误", f"无法打开文件夹报告: {str(e)}") else: messagebox.showwarning("警告", f"没有可用的{report_type}报告文件") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数""" try: report_dir = os.path.dirname(excel_file) or os.getcwd() os.makedirs(report_dir, exist_ok=True) timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") self.folder_report_path = os.path.join(report_dir, f"folder_diff_report_{timestamp}.html") # 生成比较报告 self.update_status("生成比较报告...") self.update_progress(30) winmerge_path = self.winmerge_entry.get() if not self.run_winmerge(winmerge_path, old_path, new_path): self.update_status("WinMerge执行失败") return if not os.path.exists(self.folder_report_path) or os.path.getsize(self.folder_report_path) == 0: self.log_message("警告: 文件夹报告为空或未生成") else: self.log_message(f"文件夹报告生成成功: {self.folder_report_path} ({os.path.getsize(self.folder_report_path)} bytes)") self.view_folder_report_button.config(state=tk.NORMAL) self.copy_detail_files(report_dir) self.update_status("打开Excel文件...") self.update_progress(80) if not self.open_excel_file(excel_file): self.update_status("打开Excel失败") return self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成报告并打开Excel文件") self.delete_winmerge_reports() except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: if self.processing: self.stop_processing() def delete_winmerge_reports(self): """删除WinMerge生成的报告文件、.files目录和复制的HTML文件""" if not self.folder_report_path: return # 1. 删除主报告文件 if os.path.exists(self.folder_report_path): try: os.remove(self.folder_report_path) self.log_message(f"已删除报告文件: {self.folder_report_path}") except Exception as e: self.log_message(f"删除报告文件失败: {str(e)}") # 2. 删除对应的.files目录 base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if os.path.exists(files_dir): try: shutil.rmtree(files_dir) self.log_message(f"已删除.files目录: {files_dir}") except Exception as e: self.log_message(f"删除.files目录失败: {str(e)}") # 3. 删除本次运行复制的HTML文件 report_dir = os.path.dirname(self.folder_report_path) if os.path.exists(report_dir) and hasattr(self, 'copied_html_files'): deleted_count = 0 for file_path in self.copied_html_files: if os.path.exists(file_path): try: os.remove(file_path) deleted_count += 1 self.log_message(f"已删除复制的HTML文件: {file_path}") except Exception as e: self.log_message(f"删除HTML文件失败: {file_path} - {str(e)}") self.log_message(f"已删除 {deleted_count}/{len(self.copied_html_files)} 个复制的HTML文件") # 清空列表 self.copied_html_files = [] # 4. 重置状态 self.folder_report_path = None self.view_folder_report_button.config(state=tk.DISABLED) def copy_detail_files(self, report_dir): """复制.files目录中的HTML文件到报告目录,并记录复制的文件""" base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if not os.path.exists(files_dir): self.log_message(f"警告: 详细文件目录不存在 {files_dir}") return html_files = [f for f in os.listdir(files_dir) if f.lower().endswith('.html')] if not html_files: self.log_message(f"警告: 详细文件目录中没有HTML文件 {files_dir}") return # 初始化复制的文件列表 if not hasattr(self, 'copied_html_files'): self.copied_html_files = [] copied_count = 0 for file_name in html_files: src_path = os.path.join(files_dir, file_name) dst_path = os.path.join(report_dir, file_name) try: shutil.copy2(src_path, dst_path) copied_count += 1 # 记录复制的文件路径 self.copied_html_files.append(dst_path) except Exception as e: self.log_message(f"复制文件失败: {file_name} - {str(e)}") self.log_message(f"已复制 {copied_count}/{len(html_files)} 个详细HTML文件到报告目录") def start_processing(self): """启动处理线程""" if self.processing: self.log_message("警告: 处理正在进行中") return old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") winmerge_path = self.winmerge_entry.get() if not winmerge_path or not os.path.exists(winmerge_path): validation_errors.append("WinMerge路径无效或未设置") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.DISABLED) self.processing = True thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, winmerge_path, path1, path2): """针对WinMerge 2.16.12.0优化的报告生成方法""" if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False os.makedirs(os.path.dirname(self.folder_report_path), exist_ok=True) cmd = [ winmerge_path, '/u', '/nosplash', '/dl', 'Base', '/dr', 'Modified', '/noninteractive' ] if self.recursive_var.get(): cmd.append('/r') file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": cmd.extend(['-f', file_filter]) cmd.extend(['/or', self.folder_report_path]) cmd.extend([path1, path2]) self.update_status("正在生成比较报告...") return self.execute_winmerge_command(cmd, "比较报告") def execute_winmerge_command(self, cmd, report_type): """执行WinMerge命令并处理结果""" try: self.log_message(f"开始生成{report_type}...") self.log_message(f"执行命令: {' '.join(cmd)}") start_time = time.time() process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace', creationflags=subprocess.CREATE_NO_WINDOW ) timeout = 900 try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log_message(f"{report_type}生成超时({timeout}秒),已终止进程") return False elapsed = time.time() - start_time self.log_message(f"{report_type}生成完成,耗时: {elapsed:.2f}秒") if stdout.strip(): self.log_message(f"WinMerge输出:\n{stdout[:2000]}") if stderr.strip(): self.log_message(f"WinMerge错误:\n{stderr[:1000]}") if process.returncode == 0: self.log_message(f"{report_type}命令执行成功") return True elif process.returncode == 1: self.log_message(f"{report_type}命令执行完成(发现差异)") return True else: error_msg = f"{report_type}生成失败(退出码{process.returncode})" self.log_message(error_msg) return False except Exception as e: self.log_message(f"{report_type}生成错误: {str(e)}\n{traceback.format_exc()}") return False def open_excel_file(self, excel_path): """打开Excel文件并等待用户操作""" self.log_message("正在打开Excel文件...") try: if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False os.startfile(excel_path) self.log_message(f"Excel文件已打开: {excel_path}") self.log_message("Excel已打开,请手动执行操作") messagebox.showinfo("Excel已打开", "Excel文件已打开,请手动执行所需操作") return True except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}\n{traceback.format_exc()}") return False def stop_processing(self): """停止处理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.NORMAL) self.update_status("操作已停止") def load_paths(self): """从配置文件加载保存的路径""" config = configparser.ConfigParser() if os.path.exists(self.config_file): try: config.read(self.config_file) # 原始文件夹路径 if config.has_option('Paths', 'old_folder'): old_path = config.get('Paths', 'old_folder') self.old_folder_entry.delete(0, tk.END) self.old_folder_entry.insert(0, old_path) if os.path.isdir(old_path): self.populate_folder_tree(old_path) # 修改后文件夹路径 if config.has_option('Paths', 'new_folder'): new_path = config.get('Paths', 'new_folder') self.new_folder_entry.delete(0, tk.END) self.new_folder_entry.insert(0, new_path) # Excel文件路径 if config.has_option('Paths', 'excel_file'): excel_path = config.get('Paths', 'excel_file') self.excel_file_entry.delete(0, tk.END) self.excel_file_entry.insert(0, excel_path) # WinMerge路径 if config.has_option('Paths', 'winmerge_path'): winmerge_path = config.get('Paths', 'winmerge_path') self.winmerge_entry.delete(0, tk.END) self.winmerge_entry.insert(0, winmerge_path) self.log_message("已加载上次保存的路径") except Exception as e: self.log_message(f"加载配置文件失败: {str(e)}") else: self.log_message("未找到配置文件,将使用默认路径") def save_path(self, key, path): """保存单个路径到配置文件""" config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if not config.has_section('Paths'): config.add_section('Paths') config.set('Paths', key, path) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message(f"已保存路径: {key} = {path}") except Exception as e: self.log_message(f"保存路径失败: {str(e)}") def save_all_paths(self): """保存所有路径到配置文件""" config = configparser.ConfigParser() config.add_section('Paths') config.set('Paths', 'old_folder', self.old_folder_entry.get()) config.set('Paths', 'new_folder', self.new_folder_entry.get()) config.set('Paths', 'excel_file', self.excel_file_entry.get()) config.set('Paths', 'winmerge_path', self.winmerge_entry.get()) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message("所有路径已保存") except Exception as e: self.log_message(f"保存所有路径失败: {str(e)}") def on_closing(self): """窗口关闭时的处理""" self.save_all_paths() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop() 这是我的代码·,请在我的代码的基础上进行更改
最新发布
08-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值