我建议以下代码在日志文件中产生最初想要的输出:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LOGFILE=MyLogFile.log"
del "%LOGFILE%" 2>nul
call :Logit >>"%LOGFILE%"
endlocal
exit /B 0
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
for /F "tokens=1* delims=:" %%I in ('%SystemRoot%\System32\xcopy.exe "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx*" /C /V /Y 2^>nul') do (
if not "%%J" == "" (
echo %%I:%%J
) else (
echo %FileDate% : %%I
)
)
goto :EOF
通过使用动态环境变量DATE 和TIME 的字符串替换将依赖于区域的日期和时间重新格式化为yyyyMMdd_HHmmss,例如通过问题的答案详细解释:What does %date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2% mean?速度较慢但与地区无关的解决方案,要以特定格式获取日期/时间,请参阅以下答案:Why does %date% produce a different result in batch file executed as scheduled task?
yyyyMMdd_HHmmss 格式的当前日期和时间分配给环境变量FileDate 在下一行使用了两次,一次在目标文件的名称中,一次在重新格式化的命令输出的最后一行的输出中 XCOPY。
这里使用的XCOPY命令行例如:
C:\Windows\System32\xcopy.exe "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_20180831_163959.xlsx*" /C /R /V /Y 2>nul
此命令行由 FOR 在由 FOR 启动的单独命令进程中执行,cmd.exe /C 在后台运行。 FOR 在处理捕获的行之前捕获为处理此命令进程的 STDOUT 而编写的所有行。
XCOPY 输出以处理 STDOUT 复制文件的名称以及完整路径,最后一行是摘要信息。写入文件复制错误以处理 STDERR,通过将它们重定向到设备 NUL 来抑制这些错误。
另请阅读有关Using command redirection operators 的Microsoft 文章,了解2>nul 的解释。重定向运算符 > 必须在 FOR 命令行上使用插入字符 ^ 转义,以便在 Windows 命令解释器在执行命令 FOR 之前处理此命令行时解释为文字字符> 在后台启动的单独命令进程中执行嵌入的xcopy 命令行。
目标文件名末尾的星号* 应该在第二个参数字符串的双引号内,而不是在外,否则cmd.exe 和xcopy.exe 必须更正这个错误的语法。
请注意,目标文件名末尾带有* 的技巧在这里偶然起作用,因为源文件和目标文件具有相同的文件扩展名,并且源文件名总是比目标文件名短。否则命令将失败或目标文件得到一个不需要的名称,即目标文件名 + 目标文件名 n 个字符后的源文件名字符的串联。
通常有更好的方法可以避免在使用新文件名复制单个文件时 XCOPY 请求的提示停止。回答提示的字母可以首先输出到 STDOUT 重定向到处理 XCOPY 命令的 STDIN,如在batch file asks for file or folder 的回答中证明的语言无关.
XCOPY 的捕获输出由 FOR 逐行处理,跳过空行和以分号 ; 开头的行作为默认行尾字符选项eol= 未在此处使用。
这里的目标是在后台命令进程中也输出由 XCOPY 输出的具有完全限定文件的所有行,但在此命令进程中输出带有不同摘要信息的最后一行所需格式的日期/时间、一个空格、一个冒号和一个空格。
因此,空格/制表符上的默认行拆分行为仅将第一个子字符串(标记)分配给指定的循环变量I,此处由选项tokens=1* delims=: 修改。 FOR 现在在冒号上分割一行。
只有完全限定文件名以驱动器号和冒号开头的行才包含冒号。在这样的行中,驱动器号被分配给指定的循环变量I,由tokens=1 指定。第一个冒号之后的文件名行的其余部分被分配,没有根据ASCII table 将循环变量J 分配给下一个循环变量,这里是驱动器号之后冒号之后的所有内容。
摘要信息行不包含冒号。为此,FOR 将整个摘要信息分配给循环变量I,而J 包含一个空字符串。
循环变量J 在文件名以驱动器号和冒号开头的行上从不为空。此处使用此事实来确定是否应按原样输出 XCOPY 中的行,在驱动器号和文件路径 + 文件名 + 文件扩展名之间插入已删除的冒号,或输出带有日期/时间的摘要信息一开始。
请注意,此方法仅适用于从带有驱动器号的驱动器复制文件。对于具有UNC 路径的源文件,需要使用不同的方法。
事实上,使用命令 COPY 而不是 XCOPY 可以更轻松地复制单个文件,即使从/到网络驱动器或在指定源/目标文件名时带有 UNC 路径。 COPY 还有/V 和/Y 甚至/Z 选项,例如XCOPY。 COPY 不会像 XCOPY 那样创建目标目录结构,但这可以通过之前的命令 MD 来完成。 COPY 不能像 XCOPY 在使用选项 /R 时那样覆盖只读文件,但这种 COPY 的限制很可能在这里不相关。 COPY 不会复制设置了隐藏属性的文件。但是,通常最好使用命令 COPY 而不是 XCOPY 来复制单个文件。
所以这里是使用命令 COPY 的另一种解决方案,它比 XCOPY 解决方案更快,因为没有理由在单独的命令进程中执行文件复制,捕获任何行,将它们拆分并再次连接或修改输出。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LOGFILE=MyLogFile.log"
md "D:\TL\BACKUP" 2>nul
del "%LOGFILE%" 2>nul
call :Logit >>"%LOGFILE%"
endlocal
exit /B 0
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
echo I:\DF\AB\Data.xlsx
copy /B /V /Y "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx" >nul 2>nul && echo %FileDate% : 1 File(s) copied|| echo %FileDate% : 0 File(s) copied
goto :EOF
此解决方案还有一个优点,即成功或错误的行输出可以完全自定义。 COPY 以大于 0 的值退出源文件不可用或目标文件/目录当前或永久写保护等错误。
成功或错误时单个复制文件的更好输出示例(仅限子例程):
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
copy /B /V /Y "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx" >nul 2>nul
if not errorlevel 1 (
echo %FileDate% : Copied successfully I:\DF\AB\Data.xlsx
) else (
echo %FileDate% : Failed to copy file I:\DF\AB\Data.xlsx
)
goto :EOF
当然也可以使用命令行
set "FileDate=%DATE:/=%_%TIME::=%"
如果现在真的需要,在批处理文件中以MMddyyyy_HHmmss.ms 格式获取日期和时间。我不推荐这种日期/时间格式,因为它不适用于目录D:\TL\BACKUP 中所有Data_*.xlsx 文件的字母列表。按名称排序的文件列表采用日期/时间格式yyyyMMdd_HHmmss,也会自动按日期/时间排序。
要了解所使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。
call /?
copy /?
del /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
md /?
set /?
setlocal /?
xcopy /?
另见: