简体   繁体   中英

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

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

  • Wrapping the FORFILES line in a FOR /F loop
  • Using FOR /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.
  • Using a prefix character for the files I do want to print, so that they would not be discarded by the 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)
  • Use a variable to count and increment upon each iteration. Then only print when the variable is 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 IF !COUNT! GEQ 2 ECHO %%B with IF !COUNT! GEQ 2 DEL %%B 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM