Python3增量备份目录中的文件(读取文件修改时间方式)

这是一个使用Python编写的增量备份脚本,它会监测指定目录中的文件变化,并在文件内容修改时进行备份。脚本首先进行全备份,然后通过记录和比较文件的修改时间来确定哪些文件需要增量备份。日志模块用于记录操作详情,支持在终端和日志文件中显示。此脚本适用于Linux和Windows系统。
摘要由CSDN通过智能技术生成
#!/usr/bin/python3
#_*_ coding:utf8 _*_

## V 1.1
## 增量备份目录中的文件
## 当文件修改时间改变时备份文件
## python 字典变量作为文件信息数据库 {文件ID:(修改时间, 文件路径), }
## Windows使用 '\\' 作为路径分割,Linux需要修改为 '/'

import os
import time
import pickle
import shutil
import logging                                                             # 日志模块

LOG_FILE = time.strftime('%Y%m%d')+'.log'                                  # 日志文件格式,每天一个文件
Log = logging.getLogger('__name__')                                        # 获取实例
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')   # 指定logger输出格式
file_handler = logging.FileHandler(LOG_FILE)                               # 日志文件路径
file_handler.setFormatter(formatter)                                       # 可以通过setFormatter指定输出格式
Log.addHandler(file_handler)                                               # 为logger添加的日志处理器

# 设置记录的日志级别
Log.setLevel(logging.DEBUG)
#Log.setLevel(logging.INFO)
#Log.setLevel(logging.WARNING)
#Log.setLevel(logging.ERROR)
#Log.setLevel(logging.CRITICAL)

## 同时在终端显示
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
Log.addHandler(console)

'''
参考资料

文件时间
os.stat('A:\\a.txt')
os.stat_result(st_mode=33206, st_ino=6755399441061923, st_dev=3365460235, st_nlink=1, st_uid=0, st_gid=0, st_size=11, st_atime=1650868975, st_mtime=1650868975, st_ctime=1650868939)
os.stat('A:\\a.txt')
os.stat_result(st_mode=33206, st_ino=6755399441061923, st_dev=3365460235, st_nlink=1, st_uid=0, st_gid=0, st_size=30, st_atime=1650937382, st_mtime=1650937382, st_ctime=1650868939)

在windows下一个文件有三种时间属性:
1>创建时间
2>修改时间
3>访问时间

相似的在Linux下一个文件也有三种时间属性:(与windows不同的是linux没有创建时间,而多了个访问时间)
1>访问时间(access time 简写为atime)
2>修改时间(modify time 简写为mtime)
3>状态修改时间(change time 简写为ctime)

关于Linux底下三种时间的简单介绍:
atime:(access time)显示的是文件中的数据最后被访问的时间,比如系统的进程直接使用或通过一些命令和脚本间接使用。(执行一些可执行文件或脚本)
mtime:(modify time)显示的是文件内容被修改的最后时间,比如用vi编辑时就会被改变。(也就是Block的内容)
ctime:(change time)显示的是文件的权限、拥有者、所属的组、链接数发生改变时的时间。当然当内容改变时也会随之改变(即inode内容发生改变和Block内容发生改变时)
'''


## 增量备份的基础是先全备份
def 全备份(DIR_PATH, DIR_BACKUP_ROOT, DIR_BACKUP):
    if not os.path.exists(DIR_BACKUP_ROOT):
        os.makedirs(DIR_BACKUP_ROOT)
    shutil.copytree(DIR_PATH, DIR_BACKUP_ROOT+DIR_BACKUP)	# 复制整个目录(备份)

## Python变量保存到文件
def PKL_SAVE(X, FILE_PKL):
    f = open(FILE_PKL, 'wb')
    pickle.dump(X, f)
    f.close()

## 从文件读取Python变量
def PKL_LOAD(FILE_PKL):
    f = open(FILE_PKL, 'rb')
    X = pickle.load(f)
    f.close()
    return(X)

## 对文件操作:记录文件被修改时间
def FILE_记录(FILE_PATH, D_FILE_MTIME):
    FS = os.stat(FILE_PATH)
    FILE_ID = FS.st_ino         # 文件ID
    FILE_SIZE = FS.st_size      # 文件大小
    FILE_MTIME = FS.st_mtime    # 文件修改时间
    #print(f"文件 {FILE_ID:16} 修改时间={FILE_MTIME:.8f} {FILE_PATH}")
    D_FILE_MTIME[FILE_ID] = (FILE_MTIME, FILE_PATH)    ## 记录文件修改时间和文件全路径
    if FILE_SIZE == 0:
        INFO = f"空文件 {FILE_PATH}"
        Log.info(INFO)

## 对目录操作:遍历
def DIR_遍历(DIR_PATH, D_FILE_MTIME, 目录分割符):
    L = os.listdir(DIR_PATH)
    for i in L:                                     # 获取目录里面的内容列表
        if os.path.isdir(DIR_PATH+i):               # 如果还是目录
            DIR_遍历(DIR_PATH+i+目录分割符, D_FILE_MTIME, 目录分割符) # 循环迭代进入目录
        elif os.path.isfile(DIR_PATH+i):            # 如果是文件
            FILE_记录(DIR_PATH+i, D_FILE_MTIME)     # 对文件进行操作

## 自定义备份文件名样式
def 备份文件命名(DIR_SAVE, FILE_PATH, 目录分割符):
    备份全文件名 = DIR_SAVE+time.strftime('%Y%m%d%H%M%S')+FILE_PATH.replace(':', '').replace(目录分割符, '__')
    return(备份全文件名)


## 主函数
def RUN(DIR_PATH, FILE_PKL, DIR_BACKUP_ROOT, DIR_BACKUP, 增量备份目录, 目录分割符):
    if os.path.isdir(DIR_PATH):
        ## 全备份
        if not os.path.isdir(DIR_BACKUP_ROOT+DIR_BACKUP):
            全备份(DIR_PATH, DIR_BACKUP_ROOT, DIR_BACKUP)
            INFO = f"全备份{DIR_BACKUP_ROOT}{DIR_BACKUP}"
            Log.info(INFO)
    
        ## 增量备份
        if not os.path.exists(FILE_PKL):
            D_FILE_MTIME_INIT = {}
            DIR_遍历(DIR_PATH, D_FILE_MTIME_INIT, 目录分割符)
            PKL_SAVE(D_FILE_MTIME_INIT, FILE_PKL)
            INFO = f"记录文件数量(始)={len(D_FILE_MTIME_INIT)}"
            Log.info(INFO)
        else:
            ## 读取上一次文件修改时间信息
            D_FILE_MTIME_OLD = PKL_LOAD(FILE_PKL)
            INFO = f"记录文件数量(旧)={len(D_FILE_MTIME_OLD)}"
            Log.info(INFO)
        
            ## 记录本次文件修改时间信息
            D_FILE_MTIME_NEW = {}
            DIR_遍历(DIR_PATH, D_FILE_MTIME_NEW, 目录分割符)
            PKL_SAVE(D_FILE_MTIME_NEW, FILE_PKL)    # 更新 FILE_PKL 文件
            INFO = f"记录文件数量(新)={len(D_FILE_MTIME_NEW)} 更新 FILE_PKL 文件"
            Log.info(INFO)
    
            ## 新文件信息对比旧文件信息,忽略被删除的情况
            ## 文件被修改就备份一下
            if not os.path.exists(增量备份目录):
                os.makedirs(增量备份目录)
            for FILE_ID in D_FILE_MTIME_NEW:
                FILE_MTIME_NEW, FILE_PATH_NEW = D_FILE_MTIME_NEW[FILE_ID]       # 当前检测到的文件的ID,修改时间,文件路径
                if FILE_ID in D_FILE_MTIME_OLD:                                 # 新文件有旧记录
                    FILE_MTIME_OLD, FILE_PATH_OLD = D_FILE_MTIME_OLD[FILE_ID]   # 上次检测到的文件的ID,修改时间,文件路径
                    if FILE_MTIME_NEW != FILE_MTIME_OLD:                        # 文件修改时间有变化,说明文件内容已改变
                        ## 备份被修改的文件
                        FILE_PATH_SAVE = 备份文件命名(增量备份目录, FILE_PATH_NEW, 目录分割符)
                        shutil.copy(FILE_PATH_NEW, FILE_PATH_SAVE)	            # 复制并重命名新文件
                        ## 判断是否被重命名
                        if FILE_PATH_NEW != FILE_PATH_OLD:
                            INFO = f"被修改 {FILE_PATH_OLD} 重命名或移动 {FILE_PATH_NEW} 【备份为】 {FILE_PATH_SAVE}"
                            Log.info(INFO)
                        else:
                            INFO = f"被修改 {FILE_PATH_NEW} 【备份为】 {FILE_PATH_SAVE}"
                            Log.info(INFO)
                    else:                                                       # 文件修改时间没有变,说明文件内容未改变
                        ## 判断是否被重命名
                        if FILE_PATH_NEW != FILE_PATH_OLD:
                            INFO = f"重命名 {FILE_PATH_OLD} 重命名或移动 {FILE_PATH_NEW}"
                            Log.info(INFO)
                        else:
                            INFO = f"无变化 {FILE_PATH_NEW}"
                            Log.info(INFO)
                else:                                                           # 新文件无旧记录,新增文件
                    FILE_PATH_SAVE = 备份文件命名(增量备份目录, FILE_PATH_NEW, 目录分割符)
                    shutil.copy(FILE_PATH_NEW, FILE_PATH_SAVE)	                # 复制并重命名新文件
                    INFO = f"新增   {FILE_PATH_NEW} 【备份为】 {FILE_PATH_SAVE}"
                    Log.info(INFO)
            
            ## 被删除的情况
            for FILE_ID in D_FILE_MTIME_OLD:
                FILE_MTIME_OLD, FILE_PATH_OLD = D_FILE_MTIME_OLD[FILE_ID]   # 上次检测到的文件的ID,修改时间,文件路径
                if FILE_ID not in D_FILE_MTIME_NEW:                         # 旧文件ID在新记录中找不到
                    INFO = f"被删除 {FILE_PATH_OLD}"
                    Log.info(INFO)
    else:
        ERROR = f"要备份的目录: {DIR_PATH} 不存在"
        Log.error(ERROR)


## 运行

## 根据系统类型设置目录分割符
if os.name == 'posix':  # Linux类型系统
    目录分割符 = '/'
else:
    目录分割符 = '\\'

# Windows设置
DIR_PATH = 'A:\\TEST_DIR\\'             # 要备份的目录
FILE_PKL = 'A:\\D.pkl'                  # 文件信息数据库(Python字典变量 {文件ID:(修改时间, 文件路径), })
DIR_BACKUP_ROOT = 'A:\\BACKUP\\'        # 全备份主目录
DIR_BACKUP = time.strftime('%Y%m%d')    # 全备份子目录(每天一个全备份子目录)
增量备份目录 = 'A:\\BACKUP_ADD\\'       # 增量备份目录

# Linux 设置
#DIR_PATH = '/TEST_DIR/'
#FILE_PKL = 'D.pkl'
#DIR_BACKUP_ROOT = '/BACKUP/'
#DIR_BACKUP = time.strftime('%Y%m%d')
#增量备份目录 = '/BACKUP_ADD/'

RUN(DIR_PATH, FILE_PKL, DIR_BACKUP_ROOT, DIR_BACKUP, 增量备份目录, 目录分割符)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值