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

时间:2019-06-28 16:48:34

标签: windows batch-file for-loop copy xcopy

我正在尝试将两个最近的错误日志从源位置复制到另一个易于访问的文件夹中。我在这里的Magoo's post上找到了下面的代码,其说明是将where sku <> "Orange" or qty <> 1 替换为适当的复制命令。由于某种原因,我很难过。

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

1 个答案:

答案 0 :(得分:1)

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

@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

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

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

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

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

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

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

带有选项/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>nul将在未在指定目录中找到任何文件的情况下,将 DIR 输出的错误消息从句柄 STDERR 重定向到设备 NUL 禁止显示此错误消息。

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

FOR 捕获由 DIR 编写的所有内容,以处理已启动命令过程的 STDOUT ,并在启动{{1}之后逐行处理此输出}终止。

FOR 会忽略由于 DIR 而不会在此处出现的空行,因为使用cmd.exe会输出没有空行的文件名列表。

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

文件名可以是),例如,文件名以空格和分号开头,并包含一个以上的空格和感叹号。这样的文件名将被拆分为;Test File!.log(开头没有空格)和;Test,然后由于<{1}}以分号开头而被 FOR 忽略。

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

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

出于以下原因,此处使用

XCOPY 代替 COPY

  1. XCOPY 创建目标目录的完整目录路径(如果尚不存在)。
    COPY 永远不会创建目标目录的目录结构。
  2. XCOPY 会使用已使用的参数覆盖目标目录中已经设置了只读文件属性的文件。 COPY (复制)不会覆盖只读文件。

批处理文件不会评估文件复制过程的成功与否,尽管使用I这样的附加命令行也可以实现。

对于批处理文件编写的初学者来说,下一行有点棘手。

Windows命令处理器I解析从if errorlevel 1 ...开始直至匹配的cmd.exe的整个命令块,并在该命令块中将所有出现的(环境变量引用替换为使用该命令块,在执行命令 FOR 之前,引用的环境变量的当前值。如果要在这样的命令块中修改环境变量的值,并像在此一样对环境变量)的值%variable%进行评估,则要在同一命令块中评估修改后的环境变量值,这种行为是不好的。

另请参阅How does the Windows Command Interpreter (CMD.EXE) parse scripts?

标准解决方案使用delayed expansion,如在 IF 上的命令 SET 所帮助,并在 FOR 示例输出在命令提示符窗口xx中运行。但这会导致将分配给循环变量FileCount的文件名中的所有感叹号解释为延迟的扩展环境变量引用的开始/结尾,而不是文件名的文字字符。因此,仅由于文件名或目录路径中的set /? FOR 循环将无法按预期工作。

另一种解决方案是使用命令 CALL SET 一个环境变量,并引用环境变量的值,该值的两边都带有两个百分号而不是一个。命令行

I
在运行 FOR

之前,在分析整个命令块时对

进行了修改

!

在循环的每次迭代过程中,命令 CALL 都会导致call set "FileCount=%%FileCount:~1%%" 第二次解析命令行,依此类推,第一个(最新)文件是命令 SET 是用call set "FileCount=%FileCount:~1%" 作为参数字符串执行的,因为当前值字符串的第一个字符之后只有一个cmd.exe,而在第二个文件中则是"FileCount=x",因为第一个{之后没有了字符{1}},它未定义环境变量x

因此,在复制了第二个文件之后,不再定义环境变量"FileCount=",结果 IF 条件为true,因此Windows执行 GOTO 命令命令处理器不再使用 FOR 循环,而是在带有标签x的行下面的行中继续执行批处理文件。因此,在将第二个最新文件复制到指定目标目录后,退出 FOR 循环。

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

FileCount

还有一种解决方案,也没有使用延迟扩展,我在this answer所写的Compo上看到过。

FileCount

DIR 的输出重定向到 FINDSTR ,由于正则表达式搜索字符串仅带有FileCopyDone,因此所有未过滤的行都将匹配所有行。但是由于选项@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 ,文件名的输出以递增的(行)号和开头的冒号表示。

所以 DIR 的输出就像

@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

FINDSTR 修改为

^

带有选项/N的命令 FOR 将每一行拆分为分配给循环变量Newest File.log Other File.log Oldest File.log 的冒号左边的行/文件号和右边的文件名。根据{{​​3}}表分配给下一个循环变量1:Newest File.log 2:Other File.log 3:Oldest File.log 的冒号。

将复制文件,然后进行区分大小写的字符串比较,以检查文件号是否等于分配给环境变量tokens=1* delims=:的字符串值。在相等数量的字符串上,由于已定义数量的最新文件已复制到目标,因此用命令 GOTO 退出循环。

要了解所使用的命令及其工作方式,请打开命令提示符窗口,在其中执行以下命令,并非常仔细地阅读每个命令显示的所有帮助页面。

  • H
  • I
  • FileCount
  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • findstr /?
  • goto /?
  • if /?
  • rem /?