【问题标题】:How to wait all batch files to finish before exiting?如何在退出之前等待所有批处理文件完成?
【发布时间】:2016-02-08 15:36:42
【问题描述】:

我有一个主批处理文件,而不是调用 4 个其他批处理文件,因此我们可以并行运行。

例子:

Main.bat

    start call batch1.bat
    start call batch2.bat
    start call batch3.bat
    start call batch4.bat

    exit

我希望 Main.bat 在所有 batch1 到 batch 4 都停止执行后退出。这样,我可以得到批处理文件的总运行时间。问题是 Main.bat 甚至在 batch1 到 batch4 完成执行之前就退出了。

我尝试计算每个批处理文件的 %errorlevel%,但即使 4 个 .bat 文件仍在运行,它也总是返回 0。

希望有人可以帮助我!

谢谢! :)

【问题讨论】:

    标签: windows batch-file cmd parallel-processing call


    【解决方案1】:

    我认为这是最简单最有效的方法:

    @echo off
    
    echo %time%
    
    (
        start call batch1.bat
        start call batch2.bat
        start call batch3.bat
        start call batch4.bat
    ) | set /P "="
    
    echo %time%
    

    在这个方法中,主文件中的等待状态是事件驱动,所以它不会消​​耗任何CPU时间!

    编辑添加了一些解释

    set /P 命令将在 ( block ) 中的任何命令输出一行时终止,但 start 命令在此 cmd.exe 中不显示任何行。这样,set /P 会一直等待输入,直到由start 命令启动的所有进程结束。此时,与( block ) 关联的管道已关闭,因此set /P 标准输入已关闭,set /P 命令被操作系统终止。

    【讨论】:

    • 我确实让它工作了,但每个 batch?.bat 脚本最后必须使用 EXIT 而不是 EXIT /B。此外,使用START /B 也是不可能的。似乎需要创建一个新窗口。
    • @Liturgist:恐怕我不明白你的cmets与问题的关系...... :/
    • set /P 在另一个将其StandardInput 设置为管道的 cmd 实例中运行。块中的每个start 也会产生一个 cmd 实例。但是因为 start 创建了一个新控制台(如果 /B 没有使用),Windows 将块中每个 cmd 实例上的管道 StandardOutput 替换为附加控制台屏幕缓冲区的句柄。但是它们仍然继承了管道的句柄,因此当set /P 在其一端调用ReadFile 时,仍然存在潜在的写入者和读取块,直到所有带有另一端句柄的 cmd 实例都退出为止。跨度>
    • 父级只是将CREATE_NEW_CONSOLE 标志传递给CreateProcess。孩子看到它被标记为创建一个新的控制台,因此 kernelbase 中的启动代码生成了一个 conhost.exe 实例(通过 IOCTL 到 ConDrv)。子级是使用继承的管道作为其StandardOutput 创建的(使用设置为在ntdll!RtlUserThreadStart 提前中断的调试器查看此内容),但附加到新控制台会覆盖其现有的标准句柄。它仍然具有管道的继承句柄;但是,在不知道句柄值(4 的倍数,例如 68)的情况下,它无能为力。
    • Windows 8 及更高版本使用 condrv.sys 设备驱动程序而不是 LPC。现在控制台句柄作为虚拟文件打开,例如\Device\ConDrv\Input\Device\ConDrv\Output。这些句柄是内核文件对象的普通句柄(4 的倍数),并且使用常规的 NT I/O 函数,例如 NtCreateFileNtCloseNtReadFileNtWriteFileNtDeviceIoControlFile。老式 DOS 设备实现为 CON => Device\ConDrv\ConsoleCONIN$ => \Device\ConDrv\CurrentInCONOUT$ => \Device\ConDrv\CurrentOut
    【解决方案2】:

    给新进程一个唯一的标题字符串,然后检查标题中是否有任何进程正在运行:

    start "+++batch+++" batch1.bat
    start "+++batch+++" batch2.bat
    start "+++batch+++" batch3.bat
    start "+++batch+++" batch4.bat
    :loop
      timeout /t 1 >nul
      tasklist /fi "windowtitle eq +++batch+++*" |find "cmd.exe" >nul && goto :loop
    echo all tasks finished
    

    (使用find,因为tasklist 不会返回有用的错误级别)

    【讨论】:

    • 最佳解决方案!公认的解决方案不太灵活;例如不能在启动命令之间放置 echo 语句,并且添加的 CALL 可能需要您修改 bat 文件退出值。这是完美的!
    • @gunslingor 我认为只要将 echo 语句重定向到控制台而不是 STDOUT,就可以将它们放入 Aacini 的代码中。肯定要测试的东西。
    • @Squashman ...是的,我真的不记得这个页面或两年前我在这里真正做的事情,大声笑......但似乎在发表此评论一个月后,我提供了一个我更喜欢我的应用程序的解决方案。
    • 工作得很好 - 必须适应,因为我正在运行使用 script.py 名称开始的并行 Python 脚本,所以 find 必须寻找 py.exe 而不是 cmd.exe。可耻的任务列表不会在其输出中列出窗口标题。
    • @balmy: tasklist /v 可以(但速度要慢得多,这就是我使用/fi 过滤窗口标题的原因)。
    【解决方案3】:

    试试这个。

    @echo off
    echo %time%
    start "" /wait cmd /c bat1.bat |start "" /wait cmd /c bat2.bat |start "" /wait cmd /c bat3.bat
    echo %time%
    
    pause
    

    【讨论】:

    • 这个想法是让它们并行运行,所以/wait 适得其反。
    • 非常有趣...cmd 是无穷无尽的惊喜之源 :)
    • 是的,并行运行是因为管道|不是因为start 命令!
    【解决方案4】:

    您可以让 batch1..batchn4 在完成运行时创建标志文件。

    例如 echo y > flag1 在 batch1.bat 中

    然后在主批处理文件中检查标志文件是否存在,然后退出。您将需要某种睡眠实用程序在主批处理文件的末尾执行类似的操作:

    IF EXIST flag1 GOTO check2
    sleep <for a short amount of time>
    goto check1
    
    :check2
    IF EXIST flag2 GOTO check3
    sleep <for a short amount of time>
    goto check2
    
    :check3
    IF EXIST flag3 GOTO check4
    sleep <for a short amount of time>
    goto check3
    
    :check4
    IF EXIST flag4 GOTO xit
    sleep <for a short amount of time>
    goto check4
    
    :xit
    

    这种技术的缺点是您的时间会有点偏差,因为您正在轮询标志文件而不是事件驱动。在您的情况下,这可能是也可能不是问题。批处理文件以这种方式非常有限。您最好尝试使用 PowerShell 或 python 或其他功能更强大的脚本语言来执行此操作。

    【讨论】:

    • 感谢您的回复! batch1..batchn 是否可以将某些内容返回到主批处理文件?我正在考虑将其用作检查器,但 %errorlevel% 将不起作用
    • 我不知道。您在单独的进程中启动它们,因此您无法使用环境变量来执行此操作。
    【解决方案5】:

    没有人提到这个解决方案,我认为这是最好的。将其放在脚本的末尾,根据需要修改进程名称和搜索字符串。

    这包括远程工作所需的修改,这很令人头疼。最初我使用任务列表而不是 WMIC 并检查了调用 START 时定义的命名窗口标题,但是远程使用它时任务列表中的 windowName 是 N/A。在我们的情况下,powershell 命令比 timeout 效果更好,这需要我们从 Jenkins 代理运行。

        :WAIT_FOR_FINISH
            ECHO Waiting...
            powershell -command "Start-Sleep -Milliseconds 5000" > nul
            WMIC PROCESS WHERE Name="cmd.exe" GET commandline | findstr "searchString1 searchString 2" > nul && goto :WAIT_FOR_FINISH
            ECHO.
            ECHO Processes are Complete!
            ECHO.
    

    【讨论】:

      猜你喜欢
      • 2014-01-12
      • 2020-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多