windowsbatch-filecmdio-redirectionforfiles

Windows Batch file: FORFILES "date between" workaround - how to make >CON output available to FOR loop


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%"

Solution

  • After much experimentation, I finally worked it out. Key adjustments that made it work were:

    @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.