You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

关键修改点

  1. 重命名变量source_folders为source_paths,更贴合实际用途
  2. 过滤空路径,避免无效遍历
  3. 分类型统计总文件数:单个文件直接计数+1,文件夹沿用os.walk统计逻辑
  4. 备份逻辑分支处理:
    • 单个文件:直接复制到备份文件夹,保留原文件名
    • 文件夹:沿用原有遍历复制逻辑
  5. 异常处理中增加日志文件存在性判断,避免未创建日志时的报错

内容的提问来源于stack exchange,提问作者Josh123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 10:53:12