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

Posted

技术标签:

【中文标题】如何将最近的两个日志文件复制到另一个文件夹?【英文标题】:How to copy the two most recent log files to another folder? 【发布时间】:2019-11-10 15:07:53 【问题描述】:

我正在尝试将两个最近的错误日志从源位置复制到另一个更易于访问的文件夹。我在这里的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"

【问题讨论】:

【参考方案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 了解有关命令 SETLOCALENDLOCAL 的详细信息以及使用这两个命令在后台会发生什么。

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

然后批处理文件确保在源路径和目标路径中使用\,因为这是 Windows 上的目录分隔符,而不是 Linux 和 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 的解释。当 Windows 命令解释器在执行命令 FOR 在后台启动的单独命令进程中执行嵌入的dir 命令行。

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

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

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

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

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

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

此处使用

XCOPY 代替 COPY,原因如下:

    XCOPY 创建到目标目录的整个目录路径(如果不存在)。COPY 从不创建到目标目录的目录结构。 XCOPY 用使用的参数覆盖,即使是目标目录中已设置只读文件属性的已存在文件。 COPY 永远不会覆盖只读文件。

批处理文件不会评估文件复制过程的成功或错误,尽管使用if errorlevel 1 ...之类的附加命令行也可以做到这一点。

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

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

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

标准解决方案是使用 delayed expansion,如 IF 上的命令 SET 的帮助和运行时的 FOR 示例输出命令提示符窗口set /?。但这会导致此处将分配给循环变量 I 的文件名中的所有感叹号解释为延迟扩展环境变量引用的开始/结束,而不是文件名的文字字符。所以 FOR 循环不会像预期的那样工作,只是因为文件名或目录路径中有!

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

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

在运行FOR

之前解析整个命令块被修改
call set "FileCount=%FileCount:~1%"

命令 CALL 在循环的每次迭代中通过cmd.exe 再次解析命令行等第一个(最新)文件命令 SET使用"FileCount=x" 作为参数字符串执行,因为在当前值字符串的第一个字符之后只有一个x,而在第二个文件上使用"FileCount=",因为现在在第一个x 之后没有更多字符,这会取消定义环境变量@ 987654387@.

所以在复制第二个文件后,环境变量 FileCount 不再定义,这导致 IF 条件为真,因此命令 GOTO 由 Windows 命令处理器执行不再使用 FOR 循环继续执行批处理文件,而是在带有标签 FileCopyDone 的行下方的行上。所以 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

我在Compo 写的this answer 上看到了不使用延迟扩展的另一种解决方案。

@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 将每一行拆分为分配给循环变量H 的冒号左侧的行/文件编号和分配给下一个冒号的文件名根据ASCII表循环变量I

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

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

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

【讨论】:

以上是关于如何将最近的两个日志文件复制到另一个文件夹?的主要内容,如果未能解决你的问题,请参考以下文章

如何让crontab执行脚本后并且把日志输出到另一个文件?

如何使用PowerShell将选择查询的输出从DataSet复制到日志文件?

MySql-主从复制

win7系统下mysql如何增量备份

linux 怎么将一个会更新的日志不停输出到另一个文件?

如何使用 grep 命令将 docker 日志重定向到文本文件? [复制]