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

时间:2018-06-17 02:56:40

标签: batch-file scripting io-redirection

我们有一个转换过程,它调用许多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

2 个答案:

答案 0 :(得分: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或其他文件。

答案 1 :(得分: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 循环并跳转到标签{{1} }。

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

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

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

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

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

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

    我不知道批处理文件的执行频率以及DTExec.exe完成任务所需的时间。因此,我不知道摘要日志文件中的信息重复是否会在批处理文件的实际使用中发生。例如,可以从305减少重试计数,以避免出现这种非常罕见的情况。但是,发生这种情况时不会丢失任何信息;在摘要日志文件中只记录一次批处理文件的执行。

    如果摘要日志文件当前是写保护的,那么执行命令当然要容易得多,而且只是松散所有信息。

    @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!使用延迟环境变量展开时第二次引用动态环境变量 DATE TIME

    Windows命令处理器使用语法%variable%将每个环境变量引用替换为已解析整个命令块的引用环境变量的当前值,该命令块以(开头并以匹配)结束执行命令块内的命令。因此,在此命令块中使用两次%date% %time%而不使用延迟扩展将导致在日志文件中记录两次相同的日期和时间。

    还可以通过使用一个以上的命令块将日志文件当前写保护来禁止拒绝访问错误消息,方法是将其重定向到设备 NUL

    @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
    

    注意: DTExec.exe的命令行仅包含上面发布的两个简化批处理文件中的2>&1,因为为处理 STDOUT而编写了所有内容 with command block被重定向并附加(如果可能)到日志文件。

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

    • del /?
    • echo /?
    • endlocal /?
    • for /?
    • goto /?
    • if /?
    • ping /?
    • set /?
    • setlocal /?
    • timeout /?
    • type /?
    • wmic /?
    • wmic os /?
    • wmic os get /?
    • wmic os get localdateime /?

    另见