如果批处理脚本无法写入输出文件,如何忽略错误

问题描述 投票:0回答:2

我们有一个转换过程,它调用许多Windows批处理脚本文件并将输出和错误写入单个输出文件。有时,输出文件会锁定导致脚本终止。因此,批处理文件终止并引发错误代码。如果输出文件被锁定,有没有办法忽略错误?例如,如果以下示例中的ScriptOutput文件被锁定,是否可以通过重定向输出并继续执行脚本来忽略错误?

@echo( >> D:\Conversion\log\ScriptOutput.log 2>>&1

D:\Conversion\log\ScriptOutput.log 2>>&1

@echo %date% %time% BEGIN OUTPUT FOR MoveTransDataToReporting.dtsx Script >> D:\Conversion\log\ScriptOutput.log 2>>&1
@echo(  >> D:\Conversion\log\ScriptOutput.log 2>>&1

"C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\DTExec.exe" /FILE "\"D:\Conversion\sql\MoveTransDataToReporting3Years.dtsx\"" /CHECKPOINTING OFF  /REPORTING EWCDI >> D:\Conversion\log\ScriptOutput.log 2>&1

@echo(  >> D:\Conversion\log\ScriptOutput.log 2>>&1
@echo %date% %time% END OUTPUT FOR MoveTransDataToReporting.dtsx Script >> D:\Conversion\log\ScriptOutput.log 2>>&1
@echo ************************************************************************************************************************************** >> D:\Conversion\log\ScriptOutput.log 2>>&1
batch-file scripting io-redirection
2个回答
1
投票
set "LogFile=ScriptOutput.log"

:retry
((
    REM Do stuff here...
    verify>nul  &:: Sets errolevel to 0. This is important
)>>"%LogFile%" 2>&1)2>nul || (set "LogFile=con" & echo Log file inaccessible, Bypassing LogFile... & goto :retry)

LOG块中的最后一个命令必须将errorlevel显式设置为0,因此只有在无法打开LogFile时才会执行错误条件。 verify>nul将完成这项工作。可以使用将errorlevel设置为0的任何其他命令。例如:(call )(注意call和右括号之间的空间。)

外部块将隐藏与打开LogFile相关的错误消息,因此如果需要,可以在错误条件块中显示自定义消息。

它将通过将LogFile设置为con来显示输出到控制台来重试该过程。如果需要,可以将其重定向到nul或其他文件。


0
投票

我建议以下批处理文件来解决这个问题:

@echo off
for /F "tokens=2,3 delims==.+-" %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "LogFileName=%TEMP%\%~n0_%%I%%J.log"

>>"%LogFileName%" echo/
>>"%LogFileName%" echo %date% %time% BEGIN OUTPUT FOR MoveTransDataToReporting.dtsx Script
>>"%LogFileName%" echo/

"C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\DTExec.exe" /FILE "D:\Conversion\sql\MoveTransDataToReporting3Years.dtsx" /CHECKPOINTING OFF /REPORTING EWCDI >>"%LogFileName%" 2>&1

>>"%LogFileName%" echo/
>>"%LogFileName%" echo %date% %time% END OUTPUT FOR MoveTransDataToReporting.dtsx Script
>>"%LogFileName%" echo **************************************************************************************************************************************

set "RetryCount=0"

:MergeLogs
for %%I in ("%TEMP%\%~n0_????????????????????.log") do (
    ( type "%%I" >>"D:\Conversion\log\ScriptOutput.log" ) 2>nul && (
        del "%%I" & if "%%I" == "%LogFileName%" goto :EOF
    ) || goto NextRetry
)
goto :EOF

:NextRetry
set /A RetryCount+=1
if %RetryCount% == 30 goto :EOF
echo Retry %RetryCount% to merge the logs.
if exist %SystemRoot%\System32\timeout.exe (
    %SystemRoot%\System32\timeout.exe /T 1 /NOBREAK >nul
) else (
    %SystemRoot%\System32\ping.exe -n 2 127.0.0.1 >nul
)
goto MergeLogs

第一个FOR循环在后台FOR启动的单独命令进程中执行以下命令行:

C:\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE

从该命令行输出数据的行中获取当前日期和时间,以当前微秒(实际当前毫秒)格式为YYYYMMDDhhmmssuuuuuu。有关详细说明,请参阅%date% produces different result in batch file when run from Scheduled Tasks in Server 2016上的答案。格式为YYYYMMDDhhmms的当前日期和时间分配给循环变量I,当前的微秒uuuuuu分配给循环变量J

此日期/时间字符串与%TEMP%表示的文件夹路径和%~n0表示的批处理文件的名称以及带有扩展名.log的完整限定日志文件名的附加下划线连接在一起。

因此,在批处理文件MoveData.bat的每次启动时都会在文件夹中为文件创建一个日志文件,其名称类似于MoveData_20180617105952343000.log,但批处理文件在相同的毫秒内启动两次,希望永远不会发生。

只要批处理文件中的ECHO命令行没有尾随空格/制表符,ECHO命令行就会重定向而不会在空格中留下空格。有问题的批处理代码中的ECHO命令行在重定向操作符>>作为尾随空格之前将带有空格的消息写入日志文件。

DTExec.exe的命令行中,两个\"都被删除了,因为我认为这里没有必要。同样在Windows命令行上,无法定义用双引号括起来的参数字符串,其中包含"本身作为参数字符串的文字字符。 "无法在Windows命令行中使用\^进行转义,YYYYMMDDhhmmssuuuuuu被Windows命令处理器解释为转义字符。

使用当前日期/时间(包括临时文件的文件夹中的当前毫秒)创建日志文件应该永远不会失败。

接下来,临时日志文件应附加到记录所有转换的日志文件中。例如,如果在应用程序中打开日志文件以查看其内容并且应用程序使用写锁定打开文件,则可能会失败。

因此,使用FOR循环将与临时文件的文件夹中指定通配符模式匹配的每个日志文件附加到摘要日志文件。

日期/时间格式type "%%I" >>"D:\Conversion\log\ScriptOutput.log"的主要优点是包含按字母顺序排序的此格式的日期/时间的文件名同时也按日期排序,最早的是最新的和最新的。临时文件的文件夹默认位于NTFS分区上。新技术文件系统返回与通配符模式匹配的文件名列表,该列表始终按字母顺序排序。因此,FOR循环将此批处理文件的现有临时日志文件从最旧到最新处理。

FOR循环获取临时日志文件的文件名,该文件具有此批处理文件创建的完整路径,并执行命令行(以将此日志文件的内容附加到摘要日志文件。命令行包含在)NextRetry的命令块中,以验证是否成功或失败。

成功将临时日志文件附加到摘要日志文件时,将删除临时日志文件。如果成功附加的日志文件与之前刚创建的批处理文件的日志文件相同,则退出批处理文件。否则,附加的日志文件是较旧的日志文件,在以前执行批处理文件时无法附加到摘要日志文件,因此下一个临时日志文件由FOR处理。

但是,如果将临时日志文件附加到摘要日志文件失败,因为摘要日志文件当前因任何原因而被写保护,则退出FOR循环并跳转到标签YYYYMMDDhhmmssuuuuuu

增加了重试计数器,如果批处理文件尚未尝试30次将临时日志文件附加到摘要日志文件,则它使用TIMEOUT(Windows 7或Windows Server 2008或任何更高版本的Window版本)或PING等待一秒钟(较旧的Windows版本)然后再尝试将最旧的现有临时日志文件附加到摘要日志文件。

此方法用于日志文件管理的优点:

  • 即使在执行批处理文件期间摘要日志文件被写保护,也始终会记录每个批处理文件的执行。
  • 临时创建的日志文件无法附加到摘要日志文件,尽管由于在应用程序中打开摘要日志文件已经进行了30次尝试以查看其内容几分钟,稍后将通过批处理文件附加在以后执行的任何文件上摘要日志文件不再受写保护。
  • 由于日期/时间格式DTExec.exe和NTFS返回与按字母顺序排序的模式匹配的文件名列表,在此情况下从最旧到最新的文件,以前创建的临时日志文件按正确的时间顺序附加到摘要日志文件。
  • 批处理文件仅将旧执行的临时日志文件和当前执行创建的临时日志文件附加到摘要日志文件。因此,如果批处理文件在已经运行的情况下再次执行,因此,由于DTExec.exe当前正在运行,因此创建了一个尚未包含所有信息的新临时日志文件,这个较新的临时日志文件被执行实例忽略。首先启动的批处理文件。

通过执行以下操作可能更容易理解日志文件管理:

  1. 将ECHO保留在命令行中,启动D:\Conversion\log\ScriptOutput.log并保存批处理文件。
  2. 打开命令提示符窗口并在命令提示符窗口中运行批处理文件一次以创建attrib +r D:\Conversion\log\ScriptOutput.log
  3. 执行命令attrib -r D:\Conversion\log\ScriptOutput.log以对保留摘要日志文件进行写保护,以模拟使用写锁定打开文件的应用程序。
  4. 再次从命令提示符窗口运行批处理文件。可以看出,在超过30秒的时间内附加临时日志文件会失败30次。
  5. 打开第二个命令提示符窗口并准备执行命令 DTExec.exe只需输入此命令行即可。
  6. 切换到第一个命令提示符窗口并第三次运行批处理文件。在每次尝试之间暂停1秒执行重试时,切换到第二个命令提示符窗口并执行已准备好的ATTRIB命令行以从摘要日志文件中删除只读属性。
  7. 切换回第一个命令提示符窗口,可以看到退出循环退出。
  8. D:\Conversion\log\ScriptOutput.log命令行中删除ECHO并保存批处理文件。

在文本查看器/编辑器中打开DTExec.exe,它包含所有三个批处理文件执行的信息。

只剩下一个问题了。可能会发生一个较旧的临时日志文件两次附加到摘要日志文件。在当前写保护的摘要日志文件中,在30秒内执行此批处理文件两次或多次时会发生这种情况,并且当批处理文件的运行实例全部处于重试循环且1秒超时结束时,将删除写锁定几乎同时在批处理文件的至少两个运行实例中的机会。

我不知道批处理文件的执行频率以及30完成任务需要多长时间。所以我不知道摘要日志文件中的信息重复是否会在批处理文件的实际使用中发生。例如,从5@echo off setlocal EnableDelayedExpansion ( echo/ echo %date% %time% BEGIN OUTPUT FOR MoveTransDataToReporting.dtsx Script echo/ "C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\DTExec.exe" /FILE "D:\Conversion\sql\MoveTransDataToReporting3Years.dtsx" /CHECKPOINTING OFF /REPORTING EWCDI 2>&1 echo/ echo !date! !time! END OUTPUT FOR MoveTransDataToReporting.dtsx Script echo ************************************************************************************************************************************** ) >>D:\Conversion\log\ScriptOutput.log endlocal 可以减少重试计数,以避免出现这种非常罕见的情况。但是,发生这种情况时不会丢失任何信息;只有一个批处理文件的执行在摘要日志文件中记录两次。


如果摘要日志文件当前是写保护的,那么执行命令当然要容易得多并且简单地丢失所有信息。

Delayed expansion

!date! !time!根据需要启用,通过使用%variable%使用延迟环境变量扩展,第二次引用动态环境变量DATE和TIME。

Windows命令处理器使用语法(替换每个环境变量引用,其中已引用环境变量的当前值已经解析整个命令块,该命令块以)开头,并在执行命令块内的命令之前以匹配的%date% %time%结束。因此,在此命令块中使用两次@echo off setlocal EnableDelayedExpansion (( echo/ echo %date% %time% BEGIN OUTPUT FOR MoveTransDataToReporting.dtsx Script echo/ "C:\Program Files (x86)\Microsoft SQL Server\120\DTS\Binn\DTExec.exe" /FILE "D:\Conversion\sql\MoveTransDataToReporting3Years.dtsx" /CHECKPOINTING OFF /REPORTING EWCDI 2>&1 echo/ echo !date! !time! END OUTPUT FOR MoveTransDataToReporting.dtsx Script echo ************************************************************************************************************************************** ) >>D:\Conversion\log\ScriptOutput.log ) 2>nul endlocal 而不使用延迟扩展将导致在日志文件中记录两次相同的日期和时间。

通过使用一个以上的命令块将日志文件重定向到设备NUL,还可以抑制当前写保护的日志文件上的访问被拒绝错误消息输出。

DTExec.exe

注意:2>&1的命令行最后只包含上面发布的两个简化批处理文件中的del /?,因为用命令块处理STDOUT的所有内容都被重定向并附加(如果可能)到日志文件中。


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

  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • ping /?
  • set /?
  • setlocal /?
  • timeout /?
  • type /?
  • wmic /?
  • wmic os /?
  • wmic os get /?
  • wmic os get localdateime /?
  • Where does GOTO :EOF return to?

也可以看看

© www.soinside.com 2019 - 2024. All rights reserved.