Windows 批处理文件:FORFILES "date between" 解决方法 - 如何使 >CON 输出可用于 FOR 循环

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

我创建了一个批处理文件,其中 objective 精简了旧的备份文件。更具体地说,该脚本识别最后修改日期在 183 到 365 天之间的备份文件,在整个 6 个月的时间段内每 7 天删除一个文件以外的所有文件。如果 7 天内有零个或一个文件,则不会删除任何文件。

该脚本基本上可以工作,但依赖于一个临时文件来存储每 7 天的匹配文件的文件名。我想知道是否可以修改脚本以执行相同的操作 而无需 临时文件。

该脚本从 aschipfl's answer 中描述的技术中汲取灵感来解决 FORFILES 设计缺陷。此技术有效地增强了 FORFILES,因此它可以识别上次修改的文件 两个日期(或几天)之间。我看到的困难是 FORFILES“文件已识别”输出被重定向到 CON 设备。这意味着输出不可用于 FOR /F 循环以进行进一步处理。所以我的“快速修复”是将输出重定向到 FOR /F 循环可以访问的临时文件。我想知道是否可以插入一些文件描述符魔法来利用输出到 FOR.

这是我的(非破坏性)脚本:

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

经过多次试验,我终于搞定了。使其发挥作用的关键调整是:

  • FOR /F 循环中包装 FORFILES
  • 使用 FOR /Feol=" 来抑制打印外部 FORFILES 找到的以 " 开头的不需要的文件。这取代了 >NUL 2>&1,它也意外地抑制了我想要打印的文件。
  • 为我想要打印的文件使用前缀字符,这样它们就不会被FOR /Feol="选项丢弃.几乎可以使用任何非 " 字符。我选择了波浪字符:ECHO ~DEL @FILE(行的非破坏性版本)
  • 在每次迭代时使用变量进行计数和递增。然后仅在变量为 GEQ2 时打印,以便根据需要跳过第一个找到的文件。这取代了 skip=1,它在修订后的代码中没有达到预期的效果。
@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 ---
)

示例输出:

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

要真正删除这些文件,而不是仅仅将 DEL 命令打印到控制台,请将 ECHO ~DEL 替换为 ECHO ~,并将 IF !COUNT! GEQ 2 ECHO %%B 替换为 IF !COUNT! GEQ 2 DEL %%B.

可以理解,除我以外的任何人都可能难以测试,因为需要一系列具有正确修改日期的每日备份文件。因此,如果有人有兴趣对此进行测试,这里有一些 Powershell(多么讽刺)可以创建测试文件,并将修改日期设置为过去 365 天内的每一天:

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
}

要使用,请将代码保存到某处的 .ps1 文件中,将 cd 保存到您要创建文件的目录中,然后 运行 Powershell 输入类似 powershell -ExecutionPolicy ByPass -File C:\path\to\script.ps1.