Python备份工具文件备份失效求助:仅生成日志无文件备份
问题描述
我正在开发一款Python备份工具,要求用户仅能备份文件夹或文件其中一种类型,不可同时选择。目前文件夹备份功能正常,但选择文件备份时,仅会生成包含备份起止时间的日志文件,并未实际备份目标文件。
问题原因
核心问题在于代码中统一使用os.walk()处理所有源路径,但os.walk()仅支持文件夹路径作为参数。当传入单个文件路径时,os.walk()会触发异常,而外层的try-except仅捕获错误并写入日志,没有执行文件复制逻辑,导致文件备份失效。
修复方案
修改backup()函数,增加文件与文件夹的分支处理逻辑,分别实现单个文件复制和文件夹遍历备份的功能:
import tkinter as tk import tkinter.ttk as ttk from tkinter import filedialog, messagebox import shutil import os import time import zipfile def backup(): source_paths = source_folder_text.get('1.0', 'end').strip().split('\n') # 过滤空行,避免无效路径 source_paths = [path for path in source_paths if path.strip()] dest_folder = dest_folder_text.get('1.0', 'end').strip() if not source_paths: messagebox.showerror('Error', 'Please specify at least one source path.') return if not dest_folder: messagebox.showerror('Error', 'Please specify a destination folder.') return is_file_selected = False is_folder_selected = False for path in source_paths: if os.path.isfile(path): is_file_selected = True elif os.path.isdir(path): is_folder_selected = True else: messagebox.showerror('Error', f'Invalid path: {path}') return if is_file_selected and is_folder_selected: messagebox.showerror('Error', 'Please select either files or folders for backup, not both.') return try: if not os.path.exists(dest_folder): os.makedirs(dest_folder) timestamp = time.strftime('%Y%m%d-%H%M%S') backup_folder = os.path.join(dest_folder, f'backup-{timestamp}') if not os.path.exists(backup_folder): os.makedirs(backup_folder) log_file = os.path.join(backup_folder, 'backup.log') with open(log_file, 'w') as f: f.write(f'Backup started at {time.strftime("%Y-%m-%d %H:%M:%S")}\n') total_files = 0 # 分类型统计总文件数 for path in source_paths: if os.path.isfile(path): total_files += 1 elif os.path.isdir(path): for root, dirs, files in os.walk(path): total_files += len(files) progress_window = tk.Toplevel() progress_window.title('Backup Progress') progress_label = tk.Label(progress_window, text='Backing up files...') progress_label.pack() progress_bar = tk.ttk.Progressbar(progress_window, orient='horizontal', length=300, mode='determinate', maximum=total_files) progress_bar.pack() current_file = 0 # 分类型处理备份逻辑 for path in source_paths: if os.path.isfile(path): # 处理单个文件备份 source_file = path dest_file = os.path.join(backup_folder, os.path.basename(source_file)) if os.path.exists(dest_file) and os.path.getmtime(dest_file) >= os.path.getmtime(source_file): with open(log_file, 'a') as f: f.write(f'[SKIP] {source_file} (already exists)\n') else: shutil.copy2(source_file, dest_file) with open(log_file, 'a') as f: f.write(f'[COPY] {source_file} to {dest_file}\n') current_file += 1 progress_bar['value'] = current_file progress_label.config(text=f'Backing up {source_file}...') progress_window.update() elif os.path.isdir(path): # 处理文件夹备份,沿用原有逻辑 for root, dirs, files in os.walk(path): for file in files: source_file = os.path.join(root, file) dest_file = os.path.join(backup_folder, os.path.relpath(source_file, path)) dest_dir = os.path.dirname(dest_file) if not os.path.exists(dest_dir): os.makedirs(dest_dir) if os.path.exists(dest_file) and os.path.getmtime(dest_file) >= os.path.getmtime(source_file): with open(log_file, 'a') as f: f.write(f'[SKIP] {source_file} (already exists)\n') else: shutil.copy2(source_file, dest_file) with open(log_file, 'a') as f: f.write(f'[COPY] {source_file} to {dest_file}\n') current_file += 1 progress_bar['value'] = current_file progress_label.config(text=f'Backing up {source_file}...') progress_window.update() with open(log_file, 'a') as f: f.write(f'Backup completed at {time.strftime("%Y-%m-%d %H:%M:%S")}\n') if compress_var.get(): progress_label.config(text='Compressing backup files...') zip_file = os.path.join(dest_folder, f'backup-{timestamp}.zip') with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zipf: for root, dirs, files in os.walk(backup_folder): for file in files: zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), backup_folder)) shutil.rmtree(backup_folder) messagebox.showinfo('Success', 'Backup completed successfully.') clear() except Exception as e: # 确保日志文件已创建时再写入错误信息 if 'log_file' in locals(): with open(log_file, 'a') as f: f.write(f'[ERROR] An error occurred during backup: {str(e)}\n') messagebox.showerror('Error', f'An error occurred during backup:\n{str(e)}') def browse_source_folder(): folder = filedialog.askdirectory() if folder: source_folder_text.insert('end', folder + '\n') def browse_source_file(): file = filedialog.askopenfilename() if file: source_folder_text.insert('end', file + '\n') def browse_dest_folder(): folder = filedialog.askdirectory() if folder: dest_folder_text.delete('1.0', 'end') dest_folder_text.insert('1.0', folder) def clear(): source_folder_text.delete('1.0', 'end') dest_folder_text.delete('1.0', 'end') compress_var.set(False) # Create the main window root = tk.Tk() root.title('Backup Tool') root.geometry('820x400') root.config(bg='#FFFFFF') # Create the source folder section source_folder_label = tk.Label(root, text='Source folders or files:', font=('Helvetica', 12, 'bold'), bg='#FFFFFF') source_folder_label.pack(pady=10) source_folder_text = tk.Text(root, height=5) source_folder_text.pack() select_folder_or_file_label = tk.Label(root, text='Select either a folder or a file for backup, but not both.', font=('TkDefaultFont', 10), fg='red', bg='#FFFFFF') select_folder_or_file_label.pack(pady=5) source_folder_button_frame = tk.Frame(root, bg='#FFFFFF') source_folder_button_frame.pack(pady=5) browse_source_folder_button = tk.Button(source_folder_button_frame, text='Browse Folder', command=browse_source_folder, font=('Helvetica', 10)) browse_source_folder_button.pack(side='left', padx=10) browse_source_file_button = tk.Button(source_folder_button_frame, text='Browse File', command=browse_source_file, font=('Helvetica', 10)) browse_source_file_button.pack(side='left') # Create the destination folder section dest_folder_frame = tk.Frame(root, bg='#FFFFFF') dest_folder_frame.pack(pady=10) dest_folder_label = tk.Label(dest_folder_frame, text='Destination folder:', font=('Helvetica', 12, 'bold'), bg='#FFFFFF') dest_folder_label.pack(side='left', padx=10) dest_folder_text = tk.Text(dest_folder_frame, height=1, font=('Helvetica', 10)) dest_folder_text.pack(side='left') browse_dest_folder_button = tk.Button(dest_folder_frame, text='Browse', command=browse_dest_folder, font=('Helvetica', 10)) browse_dest_folder_button.pack(side='left', padx=10) # Create the backup and compression section compress_checkbox_frame = tk.Frame(root, bg='#FFFFFF') compress_checkbox_frame.pack(pady=10) compress_var = tk.BooleanVar() compress_checkbox = tk.Checkbutton(compress_checkbox_frame, text='Compress backup files', variable=compress_var, bg='#FFFFFF') compress_checkbox.pack(side='left', padx=10) clear_button = tk.Button(compress_checkbox_frame, text='Clear', command=clear, font=('Helvetica', 10)) clear_button.pack(side='left') backup_button = tk.Button(root, text='Backup', command=backup, font=('Helvetica', 12, 'bold')) backup_button.pack(pady=10) # Start the main event loop root.mainloop()
关键修改点
- 重命名变量
source_folders为source_paths,更贴合实际用途 - 过滤空路径,避免无效遍历
- 分类型统计总文件数:单个文件直接计数+1,文件夹沿用
os.walk统计逻辑 - 备份逻辑分支处理:
- 单个文件:直接复制到备份文件夹,保留原文件名
- 文件夹:沿用原有遍历复制逻辑
- 异常处理中增加日志文件存在性判断,避免未创建日志时的报错
内容的提问来源于stack exchange,提问作者Josh123
相关产品推荐
相关产品推荐

