如何将文件夹的每个子文件夹中除最新文件外的所有文件压缩为每个子文件夹的一个 ZIP 文件?

How to compress all files with exception of newest file in each subfolder of a folder into one ZIP file per subfolder?

我正在尝试创建一个批处理脚本,它将压缩每个子目录中的所有内容,最新的(或最新的几个)除外。我目前正在 Windows 中尝试使用 7-Zip,但该目录在技术上位于 Linux 服务器上,因此欢迎针对 Linux 命令提出任何建议。

目录结构是这样的:

Directory-Parent
 ---Sub-Directory-1
 --------File1
 --------File2
 --------File3
 ---Sub-Directory-2
 --------File1
 --------File2

我想要 运行 Directory-Parent 级别的批处理,它将在除最新的 1 个文件(或可能的少数文件)之外的所有文件的每个子目录中创建一个 zip。我还想将年份添加到 zip 文件名的末尾。

所以结果是:

Directory-Parent
 -Sub-Directory-1
 --------File1
 --------Sub-Directory-12019.zip
 ---Sub-Directory-2
 --------File1
 --------Sub-Directory-22019.zip

我尝试了嵌套的 for 循环,但似乎无法实现。我在集合 (IN) 中尝试了带有 skip 和 dirfor 命令,但无法让它工作。

我目前有以下脚本。

SET theYear=2019
For /D %%d in (*.*) do 7z a "%%d\%%d%theYear%.zip" ".\%%d\*"

这一切都完成了,除了我不知道如何排除每个文件夹中的最新文件(根据最后修改时间的最新文件)。

以下代码完全未经测试,执行时请小心:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "ROOT=Directory-Parent" & rem /* (set this to `%~dp0.` to specify the parent directory of the
                              rem     batch file, or to `.` to for current working directory) */
set "YEAR=2019"  & rem // (set the year just as a constant as it is unclear where to get it from)
set "MOVA="      & rem // (set this to `-sdel` if you want the files to be moved into the archive)

rem // Interate through the current working directory:
for /D %%d in ("%ROOT%\*.*") do (
    rem // Clear exclusion parameter for `7z`, reset flag that indicates if there are files left:
    set "EXCL=" & set "FLAG="
    rem /* Iterate through all files in the currently iterated directory, sorted from 
    rem    oldest to newest (`/O:D`), regarding the last modification date/time (`/T:W`);
    rem    the `findstr` part is to exclude the archive file itself if already present: */
    for /F "delims= eol=|" %%f in ('
        dir /B /A:-D /O:D /T:W "%%~d\*.*" ^| findstr /V /I /X /C:"%%~nxd%YEAR%.zip"
    ') do (
        rem // Exclusion parameter is not yet set in first loop iteration:
        if defined EXCL set "FLAG=#"
        rem // Exclusion parameter becomes set at this point:
        set "EXCL=-x!"%%f""
    )
    rem /* Flag is only set if there is one more file besides the one to exclude,
    rem    meaning that there are at least two files in total: */
    if defined FLAG (
        rem // Store folder information in variables to avoid loss of potentially occurring `!`:
        set "FOLDER=%%~fd" & set "NAME=%%~nxd"
        rem /* Toggle delayed expansion in order to be able to read variables that have been
        rem    set within the same block of parenthesised code (namely the `for` loops): */
        setlocal EnableDelayedExpansion
        rem // Execute the `7z` command line with dynamic parameters:
        ECHO 7z a -bd %MOVA% !EXCL! -x^^!"!NAME!%YEAR%.zip" "!FOLDER!\!NAME!%YEAR%.zip" "!FOLDER!\*.*"
        endlocal
    )
)

endlocal
exit /B

为了安全起见,我在(潜在危险的)7z 命令行之前添加了 ECHO

此批处理文件可用于此任务:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FilesToIgnore=1"
set "theYear=%DATE:~-4%"
set "ListFile=%Temp%\%~n0.lst"
del "%ListFile%" 2>nul
for /D %%I in (*) do call :CompressFiles "%%I"
goto EndBatch

:CompressFiles
pushd %1
set "ZipFile=%~nx1%theYear%.zip"
for /F "skip=%FilesToIgnore% eol=| delims=" %%J in ('dir /A-D /B /O-D /TW 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /V /X /C:"%ZipFile%"') do >>"%ListFile%" echo %%J
if exist "%ListFile%" (
    echo Compressing files in directory %1 ...
    7z.exe a -bd -bso0 -i"@%ListFile%" -mx9 -scsDOS -- "%ZipFile%"
    del "%ListFile%"
)
popd
goto :EOF

:EndBatch
endlocal

批处理文件根据动态环境变量 DATE 的区域相关日期字符串动态设置环境变量 theYear。请在命令提示符下执行 window echo %DATE:~-4% 并验证输出是否为当前年份,因为 echo %DATE% 输出当前本地日期,最后四个字符为年份。

批处理文件忽略每个目录中的 FilesToIgnore 个最新文件。如果先前执行批处理文件时已经存在 ZIP 文件,也会被忽略。 ZIP 文件永远不会包含在 FilesToIgnore 中,因为已经被 findstr 过滤掉,它过滤命令 dir 的输出,它输出当前目录中没有路径的文件名,按上次修改时间排序最新文件最先输出,最旧文件最后输出。

请阅读 7-Zip 的帮助以了解使用的开关和参数。

要了解使用的命令及其工作原理,请打开命令提示符 window,在其中执行以下命令,并仔细阅读为每个命令显示的所有帮助页面。

  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • findstr /?
  • for /?
  • goto /?
  • if /?
  • popd /?
  • pushd /?
  • set /?
  • setlocal /?

阅读有关 Using Command Redirection Operators 的 Microsoft 文章,了解 2>nul| 的解释。重定向运算符 >| 必须在 FOR 命令行上使用脱字符 ^ 进行转义,以便在 [=115= 时将其解释为文字字符] 命令解释器在执行命令 FOR 之前处理此命令行,该命令在后台启动的单独命令进程中执行嵌入式命令行。

更新: 7-Zip 版本 19.00.0.0 在使用命令时输出警告并在每个子目录中创建一个空 ZIP 文件行如下所示,最初在批处理文件中使用。我首先认为这是 7-Zip 版本 19.00.0.0 的错误,因为根据它的帮助和 7-Zip,这个版本应该也支持 -- 版本 16.04.0.0 可以使用命令行:

7z.exe a -bd -bso0 -mx9 -scsDOS -- "%ZipFile%" "@%ListFile%"

必须从此命令行中删除 -- 才能使其与 7-Zip 版本 19.00.0.0 一起使用。

所以我 reported this issue 和 7-Zip 的作者很快回复解释了为什么使用 -- 会导致搜索文件名中以 @ 开头的文件,因为 7-Zip 版本 19.00.0.0:

We need some way to add @file and -file names from command line.
So -- stops @ and - parsing, and 7-Zip searches exact names.
It's more safe to use -i@listfile instead.

所以我更新了批处理文件代码并使用选项 -i 指定列表文件,这是指定列表文件最安全的方法。