我建议使用以下代码在日志文件中生成刚开始期望的输出内容。
@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在后台使用cmd.exe /C
启动的单独命令进程中执行的。 FOR在处理捕获的行之前,捕获了此命令进程的STDOUT句柄写入的所有行。
XCOPY将复制的文件名(包括完整路径)输出到STDOUT句柄,并作为最后一行输出摘要信息。文件复制错误会被写入STDERR句柄,并通过重定向到设备NUL来抑制。
还可以阅读微软关于使用命令重定向运算符的文章,以了解2>nul
的说明。在FOR命令行上,重定向运算符>
必须使用插入符号^
进行转义,以便在Windows命令解释器在执行嵌入的xcopy
命令行时,将其解释为文字字符,该命令行使用单独的命令进程在后台执行。
目标文件名末尾的星号*
应该在第二个参数字符串的双引号内,而不是外部,否则cmd.exe
和xcopy.exe
将不得不更正这个错误的语法。
请注意,在此处使用目标文件名末尾的星号*
的技巧只是偶然的,因为源文件和目标文件具有相同的文件扩展名,并且源文件名始终比目标文件名短。否则,命令将失败或目标文件将获得一个不需要的名称,即目标文件名+源文件名中目标文件名后n个字符的字符连接。
通常有更好的方法来避免在复制新文件名的单个文件时出现提示,这是XCOPY
请求的。可以首先将答案字母输出到STDOUT
并重定向到XCOPY
命令的STDIN
处理程序,如batch file asks for file or folder中所示,这是与语言无关的示例。
XCOPY 的捕获输出通过 FOR 逐行处理,跳过空行和以分号 ;
开头的行,因为这是选项 eol=
的默认行结束符,此处未使用。
这里的目标是在后台命令进程中输出所有由 XCOPY 输出的完全限定文件行,并在此命令进程中输出最后一行的汇总信息,但在其前面加上所需格式的日期/时间、一个空格、一个冒号和一个空格。
出于这个原因,通过选项 tokens=1* delims=:
修改了只将第一个子字符串(标记)分配给指定循环变量 I
的默认行拆分行为,现在 FOR 在冒号上拆分一行。
只有以驱动器字母和冒号开头的全限定文件名行才包含冒号。在这样的行上,根据 tokens=1
指定的方式,将驱动器字母分配给指定的循环变量 I
。文件名行中第一个冒号后面的其余部分将根据 ASCII 表 分配给下一个循环变量 J
,即驱动器字母后面的冒号之后的所有内容。
摘要信息行不包含冒号。因此,FOR 将整个摘要信息分配给循环变量 I
,而 J
保持为空字符串。
在以驱动器字母和冒号开头的文件名行上,循环变量 J
永远不会为空。利用这一事实,可以确定是否应该将来自 XCOPY 的行按原样输出,并在驱动器字母和文件路径 + 文件名 + 文件扩展名之间插入已删除的冒号,或者输出带有日期/时间开头的摘要信息。
请注意,此方法仅适用于从带有驱动器号的驱动器复制文件。对于具有
UNC路径的源文件,需要使用不同的方法。
实际上,即使从/到网络驱动器或使用UNC路径指定源/目标文件名,也可以使用命令
COPY更轻松地复制单个文件。
COPY还具有选项
/V
和
/Y
,甚至像
XCOPY一样具有
/Z
选项。
COPY不像
XCOPY那样创建目标目录结构,但是可以在之前使用命令
MD来完成此操作。
COPY无法覆盖只读文件,而
XCOPY可以使用选项
/R
来执行此操作,但是
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 /?
另请参阅:
echo %affix%
。这将按照您的要求设置所有内容并复制文件,但只是在调用标签时,在文件复制后会将修改时间和日期回显到日志文件中。 - Gerhard