I created a batch file with the objective of thinning out older backup files. More specifically, the script identifies backup files with a last modified date of between 183 and 365 days old, deleting all bar one file for each 7 day period within the overall 6 month period. If there are zero or one file for the 7 day period then no files are deleted.
The script basically works but relies on a temporary file for storing the filenames of the matched files for each 7 day period. I want to know if the script can be modified to do the same without the need for a temporary file.
The script takes inspiration from a technique described in aschipfl's answer to address a FORFILES
design flaw. This technique effectively enhances FORFILES
, so that it identifies files last modified between two dates (or a number of days). The difficulty as I see it is that the FORFILES
"files identified" output is redirected to the CON
device. This means that the output is not available to FOR /F
loops for further processing. So my "quick fix" was to redirect the output to a temporary file, which the FOR /F
loops have access to. I'm wondering if there's some file descriptor magic that could be inserted to avail the output to FOR
.
Here is my (non-destructive) script:
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET BACKUPFILEMASK=*.7z
SET DAYS_OLD_EARLIEST=183
SET DAYS_OLD_LATEST=365
SET TMPFILENAME=thin_out_logging.txt
FOR /L %%A IN (%DAYS_OLD_EARLIEST%,7,%DAYS_OLD_LATEST%) DO (
ECHO Iteration: %%A
SET /A ADDWEEK=%%A+7
>NUL 2>&1 FORFILES /M %BACKUPFILEMASK% /D -%%A /C "CMD /C IF @ISDIR==FALSE 2>NUL FORFILES /M @FILE /D -!ADDWEEK! || >> "%TMP%\%TMPFILENAME%" ECHO @FILE"
FOR /F %%B IN ('TYPE "%TMP%\%TMPFILENAME%" ^| FIND "" /V /C') DO SET /A LINES=%%B
ECHO Lines counted for week: !LINES!
IF !LINES! GEQ 2 (
FOR /F "skip=1 usebackq tokens=*" %%C IN ("%TMP%\%TMPFILENAME%") DO ECHO DEL %%C
)
ECHO ---
BREAK>"%TMP%\%TMPFILENAME%"
)
DEL "%TMP%\%TMPFILENAME%"
After much experimentation, I finally worked it out. Key adjustments that made it work were:
FORFILES
line in a FOR /F
loopFOR /F
with eol="
to suppress printing the unwanted files beginning with "
found by the outer FORFILES
. This replaces >NUL 2>&1
which was also undesirably suppressing the files I do want to print.FOR /F
's eol="
option. Almost any non-"
character could be used. I chose the tilde character: ECHO ~DEL @FILE
(non-destructive version of line)GEQ
to 2
, in order to skip over the first found file, as is required. This replaces skip=1
which did not have the intended effect in the revised code.@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET BACKUPFILEMASK=*.7z
SET DAYS_OLD_EARLIEST=183
SET DAYS_OLD_LATEST=365
FOR /L %%A IN (%DAYS_OLD_EARLIEST%,7,%DAYS_OLD_LATEST%) DO (
ECHO Iteration: %%A
SET /A ADDWEEK=%%A+7
SET /A COUNT=0
FOR /F ^"eol^=^"^ tokens^=^*^ delims^=^~^" %%B IN ('2^>NUL FORFILES /M %BACKUPFILEMASK% /D -%%A /C ^"CMD /C IF @ISDIR^=^=FALSE 2^>NUL FORFILES /M @FILE /D -!ADDWEEK! ^|^| ECHO ~DEL @FILE^"') DO (
SET /A COUNT+=1
IF !COUNT! GEQ 2 ECHO %%B
)
ECHO ---
)
Sample output:
Iteration: 183
DEL "Notes_backup_18112020_0837.7z"
DEL "Notes_backup_19112020_0832.7z"
DEL "Notes_backup_20112020_0844.7z"
DEL "Notes_backup_21112020_0955.7z"
DEL "Notes_backup_22112020_1339.7z"
DEL "Notes_backup_23112020_0941.7z"
---
Iteration: 190
DEL "Notes_backup_11112020_0907.7z"
DEL "Notes_backup_12112020_0930.7z"
DEL "Notes_backup_13112020_0844.7z"
DEL "Notes_backup_14112020_0916.7z"
DEL "Notes_backup_15112020_1022.7z"
DEL "Notes_backup_16112020_0905.7z"
---
To actually have those files deleted, instead of merely printing the DEL
commands to the console, replace ECHO ~DEL
with ECHO ~
, and replace IF !COUNT! GEQ 2 ECHO %%B
with IF !COUNT! GEQ 2 DEL %%B
.
Understandably this may be difficult for anyone other than me to test, because a series of daily backup files with the correct modified dates is required. So if anyone has an interest in testing this, here's some Powershell (how ironic) to create test files with the modified date set to each day in the last 365 days:
for($i=1; $i -le 365; $i++)
{
$date = (Get-Date).AddDays(-1-$i)
$filedate = $date.ToString('yyyy-MM-dd')
$file = New-Item -Path . -Name "${i}.7z" -ItemType File
$file.LastWriteTime = $date
}
To use, save the code to a .ps1
file somewhere, cd
into the directory where you want to create the files and run the Powershell by typing something like powershell -ExecutionPolicy ByPass -File C:\path\to\script.ps1
.