如何将最近的两个日志文件复制到另一个文件夹?

How to copy the two most recent log files to another folder?

我正在尝试将两个最新的错误日志从源位置复制到另一个更容易访问的文件夹。我在 Magoo's post 此处找到了下面的代码,说明是用适当的复制命令替换 echo %%i。出于某种原因,我很难做到这一点。

@ECHO OFF
SETLOCAL
SET transfer=xx
FOR /f "delims=" %%i IN ('dir/b/a-d/o-d *.*') DO IF DEFINED transfer CALL SET transfer=%%transfer:~1%%&ECHO %%i

我用 echo %%i 替换的最后一行看起来像这样:

SET transfer=%%transfer:~1%%& xcopy /y "C:\source_location" "D:\target_location"

此批处理文件可用于仅将指定源目录中的两个最新文件复制到指定目标目录的任务,而与执行批处理文件时哪个目录是当前目录无关。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileCount=xx"
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "eol=| delims=" %%I in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    call set "FileCount=%%FileCount:~1%%"
    if not defined FileCount goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

批处理文件首先根据需要在此处设置启用命令扩展的本地环境,并禁用延迟环境变量扩展,以便能够复制具有完整限定文件名(驱动器+路径+名称+扩展名)的文件包含一个或多个感叹号。请阅读 以了解有关命令 SETLOCALENDLOCAL 的详细信息以及使用这两个命令在后台发生的情况。

要复制的文件数由分配给环境变量 FileCount 的字符串的 x 个字符数决定。 xx 表示复制两个文件,xxxx 表示复制四个文件。在分配给环境变量 FileCount 的字符串中使用哪个字符并不重要,字符串的长度很重要,它必须至少是一个字符。

然后批处理文件确保在源路径和目标路径中使用 \,因为这是 Windows 上的目录分隔符,而不是 [=335 上的 / =] 和 Mac.

下一个源和目标路径在批处理文件中定义。这两个环境变量也可以动态定义,而不是固定,方法是将传递给批处理文件的第一个和第二个参数分配给这两个环境变量。

批处理文件是为始终以 Windows 目录分隔符 \ 结尾的源路径编写的,因此批处理文件确保源路径的最后一个字符确实是反斜杠。

目标路径必须以反斜杠结尾。这对于将它用作命令 XCOPY 的目标字符串非常重要,正如我在 batch file asks for file or folder 上的回答中非常详细地解释的那样。因此,批处理文件确保目标路径也以反斜杠结尾。

带有选项 /F 的命令 FOR 启动一个新的命令进程,其中 %ComSpec% /c' 之间指定的命令行作为进一步的参数在后台。因此 FOR 执行的是通常的 Windows 安装路径:

C:\Windows\System32\cmd.exe /c dir "C:\source_location\" /A-D /B /O-D 2>nul

DIR 由后台命令进程执行搜索指定参数

  • 在指定的源码目录下
  • 因为选项 /A-D 的文件(属性不是目录)
  • 匹配默认通配符模式*(全部)

并输出

  • 由于选项 /B 为裸格式,只是没有路径的文件名从未包含在 "
  • 由于选项 /O-D 而未使用选项 /TC(创建日期)或 /TA(最后访问日期),因此按最后修改日期反向排序,这意味着首先是最新修改的文​​件最后一个最旧的修改文件。

DIR 的输出被写入处理已启动后台命令进程的 STDOUT

2>nulDIR 在指定目录中找不到任何文件的错误消息从句柄 STDERR 重定向到设备NUL 以抑制此错误消息。

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

FOR 捕获由 DIR 编写的所有内容以处理启动命令进程和进程的 STDOUT开始后逐行输出 cmd.exe 自行终止。

FOR 忽略此处不出现的空行,因为 DIR 输出没有空行的文件名列表,因为使用 /B.

FOR 默认情况下会将一行拆分为子字符串(标记),使用普通 space 和水平制表符作为分隔符。完成此子字符串拆分后,FOR 将默认检查第一个子字符串是否以默认行尾字符 ; 开头,在这种情况下,该行将像空行一样被忽略。否则 FOR 会将第一个 space/tab 分隔字符串分配给指定的循环变量 I 并会在 ( 和匹配之间的命令块中执行命令行).

文件名可以是例如 ;Test File!.log,即文件名以 space 和一个分号开头,再包含一个 space 和一个感叹号。这样的文件名将被拆分为;Test(开头没有 space)和 File!.log,接下来被 FOR 忽略,因为 ;Test 以分号开头。

因此,行尾字符从默认的分号重新定义为带有 eol=| 的竖线,根据有关 Naming Files, Paths, and Namespaces 的 Microsoft 文档,这是一个文件或文件夹名称不能包含的字符。并且在 for /F 之后的选项参数字符串末尾使用 delims= 禁用了行拆分行为,它定义了一个空的分隔符列表。因此 DIR 输出的文件名被分配给循环变量 I 即使是一个非常不寻常的文件名也没有任何修改。

文件名和扩展名没有路径分配给循环变量I的文件用命令XCOPY复制到指定的目标目录并保留其名称和扩展。

这里使用

XCOPY代替COPY,原因如下:

  1. XCOPY 创建到目标目录的整个目录路径(如果不存在的话)。
    COPY 从不创建目标目录的目录结构。
  2. XCOPY 使用使用的参数覆盖甚至目标目录中已经存在的文件已经设置了 read-only 文件属性。 COPY 从不覆盖 read-only 文件。

文件复制过程的成功或错误不会由批处理文件评估,尽管这也可以通过附加命令行如 if errorlevel 1 ....

下一行对于批处理文件编写的初学者来说有点难以理解。

Windows 命令处理器 cmd.exe 解析以 ( 开始直至匹配 ) 的整个命令块,并在此命令块中替换所有出现的 %variable% 在执行命令 FOR 之前引用的环境变量的当前值引用环境变量使用此命令块。如果在此类命令块中修改环境变量的值并在与此处对环境变量 xx 的值 xx 执行的相同命令块中评估修改后的环境变量值,则此行为不好。

另见 How does the Windows Command Interpreter (CMD.EXE) parse scripts?

标准解决方案是使用 delayed expansion,如 SETIFFOR 运行 在命令提示符 window set /? 上的示例输出。但这会导致将分配给循环变量 I 的文件名中的所有感叹号解释为延迟扩展环境变量引用的 begin/end,而不是文件名的文字字符。因此 FOR 循环不会按预期工作,只是因为文件名或目录路径中有 !

另一种解决方案是使用命令 CALLSET 一个环境变量,并在每边用两个百分号引用环境变量值只有一个。命令行

call set "FileCount=%%FileCount:~1%%"

在运行FOR之前解析整个命令块被修改为

call set "FileCount=%FileCount:~1%"

命令 CALL 在循环的每次迭代期间通过 cmd.exe 第二次解析命令行,依此类推,第一个(最新的)文件命令 SET"FileCount=x" 作为参数字符串执行,因为在当前值字符串的第一个字符之后只有一个 x 并且在第二个文件中使用 "FileCount=" 作为参数字符串现在在第一个 x 之后不再有字符,这取消了环境变量 FileCount.

的定义

因此,在复制第二个文件后,环境变量 FileCount 不再定义,这导致 IF 条件为真,因此命令 GOTO 由 Windows 命令处理器执行,不再使用 FOR 循环继续执行批处理文件,而是在带有标签 [=73 的行下方的行=].因此,在将第二个最新文件复制到指定的目标目录后,FOR 循环退出。

只有当两个目录路径和所有要复制的文件不包含感叹号时,这里是使用延迟扩展的解决方案。

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set FileCount=2
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "eol=| delims=" %%I in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    set /A FileCount-=1
    if !FileCount! == 0 goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

我在 written by Compo.

上看到了另一种不使用延迟扩展的解决方案
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileCount=2"
set "SourcePath=C:\source_location"
set "TargetPath=D:\target_location"

set "SourcePath=%SourcePath:/=\%"
set "TargetPath=%TargetPath:/=\%"

if not "%SourcePath:~-1%" == "\" set "SourcePath=%SourcePath%\"
if not "%TargetPath:~-1%" == "\" set "TargetPath=%TargetPath%\"

for /F "tokens=1* delims=:" %%H in ('dir "%SourcePath%" /A-D /B /O-D 2^>nul ^| %SystemRoot%\System32\findstr.exe /N "^"') do (
    %SystemRoot%\System32\xcopy.exe "%SourcePath%%%I" "%TargetPath%" /C /I /Q /H /R /Y >nul
    if %FileCount% == %%H goto FileCopyDone
)

:FileCopyDone
rem Other commands can be inserted here.
endlocal

DIR 的输出被重定向到 FINDSTR,它输出所有未过滤的行,因为正则表达式搜索字符串只有 ^ 会在所有行上产生正匹配。但是由于选项/N.

,文件名输出时带有递增的(行)号和冒号。

所以 DIR 的输出像

Newest File.log
Other File.log
Oldest File.log

FINDSTR修改为

1:Newest File.log
2:Other File.log
3:Oldest File.log

带有选项 tokens=1* delims=: 的命令 FOR 将每一行拆分为分配给冒号的 line/file 数字根据 ASCII table.

循环变量 H 和分配给下一个循环变量 I 的冒号文件名权

复制文件,然后进行 case-sensitive 字符串比较以检查文件编号是否等于分配给环境变量 FileCount 的字符串值。在相同数量的字符串上,使用命令 GOTO 退出循环,因为已将定义数量的最新文件复制到目标。

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

  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • findstr /?
  • goto /?
  • if /?
  • rem /?
  • set /?
  • setlocal /?
  • xcopy /?