今天在百度知道看到这样一个问题:
批处理只删除一个众多的文件夹里,昨天的文件夹?
http://zhidao.baidu.com/question/689168739848712772
我在该问题下做了简短回答并分享了我以前整理的代码;
细想这个问题,以及当初遇到同样需求时的处理过程,就想回过头来再掰扯掰扯;
掰扯的目的是为了能够理解批处理如何实现批量删除过期文件(夹)的过程。
翻开我的笔记,记录日期停留在2019年10月16日。
当时遇到的问题是公司一个项目每天的备份计划失败了,查看原因,原来是硬盘空间满了导致备份失败;
后来增加了NAS对每天备份的数据进行同步,但服务器自身的备份数据在同步后仍留在硬盘上,有什么办法能够对这些历史数据进行批量删除,仅保留最近60天的数据呢?
对于项目所在的Windows操作系统,我想通过 计划任务+批处理 的组合,来完成这项任务。
声明:
任何因使用本博文内的任何代码造成的任何损失或危害,都与本人无关,继续阅读说明您已知晓此声明!
1、分析问题
昨天的文件夹、仅保留最近60天的数据(或者理解为删除超过60天的数据),这些表述的文件(夹)正如标题中拟定的“过期”之意。
怎么筛选出这些过期的文件(夹)?
需要注意,这个过期是针对文件(夹)的创建日期还是修改日期?
相对于我这个项目备份生成的数据,一旦备份生成就不会再动,其创建日期和修改日期一致。
所以需要具体情况具体对待。
2、解决问题
2.1 Forfiles
这是最初找到的适合于在批处理中操作的一个命令。
其帮助信息点击Microsoft Doc:Forfiles,可进一步了解,这里给几个示例:
forfiles /p "e:\bak\project1" /s /d -60 /c "CMD /C ECHO @path"
这句的作用是列出E盘bak\project1目录(/p)及子目录(/s)下,修改日期至今超过60天(/d -60)的所有文件(夹),并输出文件的完整路径。
forfiles /p "e:\bak\project1" /s /d -60 /c "CMD /C IF @ISDIR==TRUE ECHO @PATH"
这句话在前面一句命令上,加了个@ISDIR==TRUE判断(实测 TRUE 必须大写)
作用是仅输出所有符合条件的文件夹的路径
至此,要删除超过60天的历史数据,似乎有了办法
REM 筛选出超过60天的文件夹信息放入临时文件expired_dirs.txt forfiles /p "e:\bak\project1" /s /d -60 /c "CMD /C IF @ISDIR==TRUE ECHO @PATH" | sort /r > e:\expired_dirs.txt REM 逐条读取信息并执行静默删除 REM RD删除命令很危险,测试时最好用echo命令多尝试,确认后使用RD For /f "tokens=*" %%i in (d:\expired_dirs.txt) DO RD /S /Q "%%i"
那么,只想删除昨天的文件夹,怎么处理?
forfiles /p "e:\bak\project1" /s /d -1 /c "CMD /C IF @ISDIR==TRUE ECHO @PATH"
这条命令能不能只筛选出昨天的文件夹?
肯定不行,这句的作用只是列出早于昨天(/d -1)的所有文件夹。
那么,批处理里,怎么处理像昨天、前天这种特指某一天的数据呢?
2.2 儒略日
任何操作系统都要有一套时间格式,但似乎并没有真正的标准,而儒略日是天文学中的一种时间测量方法,其以公元前4713年1月1日UTC的中午为起始测量时间点,并使用浮点值以天为单位测量时间。其近乎于标准!
例如今天(2021-4-16)的儒略日为2459321,其计算公式:
JDN = (1461 × (Y + 4800 + (M − 14)/12))/4 +(367 × (M − 2 − 12 × ((M − 14)/12)))/12 − (3 × ((Y + 4900 + (M - 14)/12)/100))/4 + D − 32075
批处理实现的代码片断:
REM 返回给定日期的儒略日(Julian Day Number) REM 需注意系统日期的格式 yyyy/mm/dd :DateToJDN "yyyy/mm/dd DD" jdn= setlocal set date=%~1 set /A yy=%date:~0,4%, mm=1%date:~5,2% %% 100, dd=1%date:~8,2% %% 100 set /A a=mm-14, jdn=(1461*(yy+4800+a/12))/4+(367*(mm-2-12*(a/12)))/12-(3*((yy+4900+a/12)/100))/4+dd-32075 endlocal & set %2=%jdn% exit /B
以上代码片断的作用是定义一个DateToJDN函数,用以转换输入时间所对应的儒略日
SET TODAY=%date:~0,10% CALL :DateToJDN "%TODAY%" JDNToday echo !JDNToday!
执行上面的代码,可以计算出今天(2021-4-16)的儒略日为2459321
那么昨天的儒略日为2459320,前天的儒略日为2459319,这很好理解吧!
反过来,如果要筛选出昨天的文件夹,只要将文件夹的创建日期转换成儒略日,并计算与今天对应的儒略日之间的差值是否为1。
2.3 基于儒略日的代码实现
dir /ad /tc "e:\bak\project1"
列出E盘bak\project1目录下的所有目录信息,输出示例:
驱动器 E 中的卷没有标签。 卷的序列号是 1911-FB58 E:\bak\project1 的目录 2021/03/09 11:24 <DIR> . 2021/03/09 11:24 <DIR> .. 2021/03/09 11:24 <DIR> 20210309 2021/03/10 21:15 <DIR> 20210310 2021/03/11 21:15 <DIR> 20210311 2021/03/12 21:15 <DIR> 20210312 2021/03/13 21:15 <DIR> 20210313 ...... 2021/04/13 21:15 <DIR> 20210413 2021/04/14 21:15 <DIR> 20210414 2021/04/15 21:15 <DIR> 20210415
清理输出结果中的无用信息
dir /ad /tc "e:\bak\project1"^| findstr "^[0-9]"
2021/03/09 11:24 <DIR> . 2021/03/09 11:24 <DIR> .. 2021/03/09 11:24 <DIR> 20210309 2021/03/10 21:15 <DIR> 20210310 2021/03/11 21:15 <DIR> 20210311 2021/03/12 21:15 <DIR> 20210312 2021/03/13 21:15 <DIR> 20210313 ...... 2021/04/13 21:15 <DIR> 20210413 2021/04/14 21:15 <DIR> 20210414 2021/04/15 21:15 <DIR> 20210415
现在,怎么逐条拿出每个文件夹的日期值并转换成儒略日以进行差值计算?这里要用到For命令
SET FOLDER=E:\bak\project1 SET TODAY=%date:~0,10% REM 取今日的儒略日 赋值给 JDNToday CALL :DateToJDN "%TODAY%" JDNToday REM 循环取 E:\bak\project1 目录下 各子目录的 创建日期 echo 创建日期 儒略日 差值 FOR /f "tokens=1,4*" %%I IN ('dir /ad /tc "%FOLDER%"^| findstr "^[0-9]"') DO ( REM 取子目录的儒略日 赋值给 filedate CALL :DateToJDN "%%I" filedate REM 计算与今日的儒略日之间的差值 SET /a diffdays=JDNToday-filedate echo %%I !filedate! !diffdays! )
上面的代码,会输出下面的结果:
创建日期 儒略日 差值 2021/03/09 2459283 38 2021/03/09 2459283 38 2021/03/09 2459283 38 2021/03/10 2459284 37 2021/03/11 2459285 36 2021/03/12 2459286 35 2021/03/13 2459287 34 ...... 2021/04/13 2459318 3 2021/04/14 2459319 2 2021/04/15 2459320 1
输出的结果给出了各子目录的创建日期与今日的儒略日之间的差值。
最后,要取昨天的文件夹,就要判断差值是否等于1
简单地补充一下,批处理中的关系运算符:
EQU(=), NEQ(<>), LSS(<), LEQ(<=), GTR(>), GEQ(>=)
IF !diffdays! EQU 1 ECHO "这是昨天创建的文件夹"
相关的点是不是都齐了?!最后我们整理一下完整 的代码:
@ECHO OFF & SETLOCAL ENABLEDELAYEDEXPANSION REM 删除指定天数前创建的目录(所有子目录及文件) REM 指定维护的目录 SET FOLDER=%TEMP% REM 设置超限天数 SET /a XDay= 1 REM 记录日志 SET SHECULED_LOG_DIR=%TEMP% IF NOT EXIST %SHECULED_LOG_DIR% MKDIR %SHECULED_LOG_DIR% SET TODAY=%date:~0,10% SET DATE_STR=%TODAY:/=% SET LOG=%SHECULED_LOG_DIR%\rd-expired-directories-%DATE_STR%.LOG rem echo Start ------ (%date% %time%) ------ >> %LOG% rem echo Get All Expired Directories >> %LOG% CALL :DateToJDN "%TODAY%" JDNToday FOR /d %%a in ("%FOLDER%\*") DO ( FOR /f "tokens=1,4*" %%I IN ('dir /ad /tc "%%~a"^|findstr "^[0-9]"') DO ( CALL :DateToJDN "%%I" filedate SET /a diffdays=JDNToday-filedate REM echo %%I !filedate! !diffdays! REM 删除指令: RD /S /D REM 在确认安全的前提下,替换下方的 ECHO IF !diffdays! EQU %XDay% ECHO "%%~a\%%~J" >> %LOG% ) ) rem echo Done ------ (%date% %time%) ------ >> %LOG% GOTO :eof REM 返回给定日期的儒略日(Julian Day Number) REM https://en.wikipedia.org/wiki/Julian_day :DateToJDN "yyyy/mm/dd DD" jdn= setlocal set date=%~1 set /A yy=%date:~0,4%, mm=1%date:~5,2% %% 100, dd=1%date:~8,2% %% 100 set /A a=mm-14, jdn=(1461*(yy+4800+a/12))/4+(367*(mm-2-12*(a/12)))/12-(3*((yy+4900+a/12)/100))/4+dd-32075 endlocal & set %2=%jdn% exit /B
注:
其他的辅助性说明已在完整代码里进行注释;
增加了日志记录,日志文件位于系统临时目录(%TEMP%);
RD /S /Q 是静默方式的递归删除命令,须严格确认安全的前提下再使用;
补充:
如要通过该段代码取60天前的数据,怎么操作?
其实只要修改两处:
1. 修改xDay = 60
2. 修改关系运行符EQU => GTR
IF !diffdays! GTR %XDay% ECHO "%%~a\%%~J" >> %LOG%
[via]
https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/