【问题标题】:execute batch files in parallel and get exit code from each并行执行批处理文件并从每个批处理文件中获取退出代码
【发布时间】:2017-04-23 16:22:27
【问题描述】:

如何从单个批处理文件中并行执行一组批处理文件并从每个批处理文件中获取退出代码。当我使用 start 它并行执行批处理文件(新的 cmd 窗口),但不从每个返回退出代码。在使用调用时,我可以获得退出代码,但批处理文件执行是按顺序发生的。我有以下代码:

ECHO ON
setlocal EnableDelayedExpansion
sqlcmd -S server_name -E  -i select_code.sql  >\path\output.txt

for /f "skip=2 tokens=1-3 delims= " %%a in ('findstr /v /c:"-"  \path\output.txt') do (

echo $$src=%%a>\path1\%%c.prm
echo $$trg=%%b>>\path1\%%c.prm
set param_name=%%c

start cmd \k  \path\exec_pmcmd_ctrm.bat workflow_name  %%param_name%%
ping 1.1.1.1 -n 1 -w 5000 > nul
set exitcode="%V_EXITCODE%"
echo %exitcode%>>\path\exitcode.txt
)   

这将使用不同的变量并行执行 exec_pmcmd_ctrm.bat 3 次,但我无法从每次执行中获取退出代码。我尝试使用 call 但后来我错过了 bat 文件的并行执行。在这方面有什么帮助吗?

【问题讨论】:

  • 您需要另一种方法来收集退出代码。如果您使用 start 异步运行程序,则没有单点可以接收退出代码。我清楚地记得 Frank P. Westlake 使用 NamedPipes/MailSlots 编写的一个工具,它可以用于此目的。它是免费的,但我不知道它是否仍然可以下载。 msdn:About Mailslots
  • 你从哪里得到的%V_EXITCODE%

标签: windows batch-file cmd parallel-processing


【解决方案1】:

这是一个纯粹的 解决方案。基本上,此脚本同时执行位于同一目录中的所有批处理文件,其中每个批处理文件的退出代码 (ErrorLevel) 写入单独的日志文件(与批处理文件同名,扩展名为 .log);检查这些文件是否存在;一旦找到这样的日志文件,就读取存储的退出代码并将其与相应的批处理文件名一起复制到摘要日志文件中;处理完所有日志文件后,该脚本将终止;仅当执行的批处理文件的所有退出代码也为零时,此脚本的退出代码才为零。所以这里是代码——查看所有解释性rem 备注:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Collect available scripts in an array:
set /A "INDEX=0"
for %%J in ("%~dp0*.bat" "%~dp0*.cmd") do (
    if /I not "%%~nxJ"=="%~nx0" (
        set "ITEM=%%~fJ"
        call set "$BATCH[%%INDEX%%]=%%ITEM%%"
        set /A "INDEX+=1"
    )
)

rem // Execute scripts simultaneously, write exit codes to individual log files:
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
    start "" /MIN cmd /C rem/ ^& "%%~fJ" ^& ^> "%%~dpnJ.log" call echo %%^^ErrorLevel%%
)

rem // Deplete summary log file:
> "%~dpn0.log" rem/

rem // Polling loop to check whether individual log files are available:
:POLLING
rem // Give processor some idle time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished script,
rem    so its log file is availabe, the related array element becomes deleted,
rem    so finally, there should not be any more array elements defined: */
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
    rem // Suppress error message in case log file is not yet available:
    2> nul (
        rem // Read exid code from log file:
        set "ERRLEV=" & set "FILE="
        < "%%~dpnJ.log" set /P ERRLEV=""
        if defined ERRLEV (
            rem // Copy the read exit code to the summary log file:
            set "NAME=%%~nxJ"
            >> "%~dpn0.log" call echo(%%ERRLEV%%    "%%NAME%%"
            rem // Undefine related array element:
            set "%%I="
            rem // Store log file path for later deletion:
            set "FILE=%%~dpnJ.log"
        )
        rem // Delete individual log file finally:
        if defined FILE call del "%%FILE%%"
    )
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set $BATCH[ && goto :POLLING

rem // Check individual exit codes and return first non-zero value, if any:
set "ERRALL="
for /F "usebackq" %%I in ("%~dpn0.log") do (
    if not defined ERRALL if %%I neq 0 set "ERRALL=%%I"
)
if not defined ERRALL set "ERRALL=0"

endlocal & exit /B %ERRALL%

这种方法与我在下面的Improving Batch File for loop with start subcommand 中使用的方法非常相似,但是会收集同时执行的命令的输出。

【讨论】:

  • 感谢@aschipfl .. 我根据上面的脚本使它工作。现在只是一个小疑问..我在一个txt文件中收集了一些参数,需要将它们逐行传递给存储的过程。尝试下面的代码:for /f "tokens=1-3 delims=," %%a in ('\path\batch_output.txt') do( sqlcmd -S servername -E -i path\spu_update_src_trg_ref.sql -v %%a,%%b,%%c),但在此处出现“do(”错误是意料之外的。
  • 我尝试过使用这种方法,但我不清楚这个脚本本身如何使用下标的错误代码来返回成功或失败。换句话说,尽管下标成功,但此脚本始终返回 exitcode > 0。关于如何改变这一点的任何想法?
  • 该脚本旨在将其执行的子脚本的退出代码收集到一个日志文件中,但它自己的退出代码不依赖于它们,它始终是1,因为最后一个set $BATCH[ 命令;如果所有子脚本也都返回零,我将尝试更新脚本,使其退出代码为零...
  • @boojum,我现在稍微扩展了脚本,所以它的退出代码取决于子脚本...
【解决方案2】:

首先,另一个批处理文件(以exit /B value 命令结尾)的“退出代码”是通过%ERRORLEVEL% 变量(不是%V_EXITCODE%)获取的。此外,如果这样的值在FOR 循环内部发生变化,则必须通过延迟扩展来代替:!ERRORLEVEL!(以及程序开头的 EnableDelayedExpansion)。但是,这些点并不能解决您的问题,因为这里存在误解...

当使用START 命令时(没有/WAIT 开关),一个并行 cmd.exe 进程开始执行。这意味着没有一种直接的方式,第一个批处理文件可以知道并行批处理在哪个时刻结束,以便在该点获得其 ERRORLEVEL!没有“等待启动的批处理文件”命令,但即使存在,也不能解决有几个并发批处理文件的问题。换句话说,你的问题不能通过直接命令解决,所以需要变通。

最简单的解决方案是并行批处理文件将其 ERRORLEVEL 值存储在一个文件中,该文件稍后可以由原始批处理文件读取。这样做意味着存在同步问题,以避免同时对同一个文件进行写访问,但那是另一回事了...

【讨论】:

  • 您可以避免任何同步问题,方法是让每个批处理文件写入不同的文件(可以作为命令行参数提供)并在所有操作完成后从它们收集数据
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
  • 2017-03-07
  • 1970-01-01
相关资源
最近更新 更多