You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Batch脚本中实现两个独立控制的实时进度条?

实现双独立实时进度条的Batch脚本

当然可以实现两个独立的进度条,分别显示当前函数进度和脚本整体进度。你之前复制子例程后失败,核心问题是原代码仅用回车符回到行首,无法跨行列控制光标,且变量隔离需要彻底。下面是修改后的完整解决方案:

核心改动说明

  • 启用ANSI光标控制码:Windows 10及以上支持通过ANSI序列移动光标,实现多行进度条的独立更新
  • 完全隔离两个进度条的变量:将第二组子例程的所有pb.前缀变量改为pb1.,子例程名后缀加1
  • 控制光标在两行之间切换:通过ANSI转义码实现光标上下移动,分别更新对应进度条

完整代码

@echo off
setlocal enableextensions enabledelayedexpansion

:: 启用ANSI虚拟终端支持(仅Windows 10+需要,执行一次即可)
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1 /f >nul 2>&1
set "ESC=[

:: 初始化两个进度条
call :initProgressBar "#" "."
call :initProgressBar1 "=" " "

:: 模拟3个任务,整体进度按任务数推进
set "totalTasks=3"
set /a "taskStep=100/totalTasks"

:: 任务1:当前进度条从0到100,整体进度0%
set /a "overallProgress=0"
call :drawProgressBar 0 "正在执行任务1"
call :drawProgressBar1 !overallProgress! "脚本整体进度"
for /l %%i in (0 1 100) do (
    call :drawProgressBar %%i "正在执行任务1"
    !ESC!1B  :: 光标下移一行到整体进度条
    call :drawProgressBar1 !overallProgress! "脚本整体进度"
    !ESC!1A  :: 光标上移一行回到当前进度条
    timeout /t 0.01 /nobreak >nul
)

:: 任务2:当前进度条重置从0到100,整体进度33%
set /a "overallProgress+=taskStep"
call :drawProgressBar 0 "正在执行任务2"
!ESC!1B
call :drawProgressBar1 !overallProgress! "脚本整体进度"
!ESC!1A
for /l %%i in (0 1 100) do (
    call :drawProgressBar %%i "正在执行任务2"
    !ESC!1B
    call :drawProgressBar1 !overallProgress! "脚本整体进度"
    !ESC!1A
    timeout /t 0.01 /nobreak >nul
)

:: 任务3:当前进度条重置从0到100,整体进度66%→100%
set /a "overallProgress+=taskStep"
call :drawProgressBar 0 "正在执行任务3"
!ESC!1B
call :drawProgressBar1 !overallProgress! "脚本整体进度"
!ESC!1A
for /l %%i in (0 1 100) do (
    call :drawProgressBar %%i "正在执行任务3"
    set /a "tempOverall=overallProgress + (%%i * taskStep)/100"
    !ESC!1B
    call :drawProgressBar1 !tempOverall! "脚本整体进度"
    !ESC!1A
    timeout /t 0.01 /nobreak >nul
)

:: 结束后清理进度条
call :finalizeProgressBar 1
call :finalizeProgressBar1 1
echo 所有任务执行完成!

endlocal
exit /b

:: -------------------------- 第一个进度条子例程(当前函数进度) --------------------------
:drawProgressBar value [text]
    if "%~1"=="" goto :eof
    if not defined pb.barArea call :initProgressBar
    setlocal enableextensions enabledelayedexpansion
    set /a "pb.value=%~1 %% 101", "pb.filled=pb.value*pb.barArea/100", "pb.dotted=pb.barArea-pb.filled", "pb.pct=1000+pb.value"
    set "pb.pct=%pb.pct:~-3%"
    if "%~2"=="" ( set "pb.text=" ) else ( 
        set "pb.text=%~2%pb.back%" 
        set "pb.text=!pb.text:~0,%pb.textArea%!"
    )
    <nul set /p "pb.prompt=[!pb.fill:~0,%pb.filled%!!pb.dots:~0,%pb.dotted%!][ %pb.pct% ] %pb.text%!pb.cr!"
    endlocal
    goto :eof

:initProgressBar [fillChar] [dotChar]
    if defined pb.cr call :finalizeProgressBar
    for /f %%a in ('copy "%~f0" nul /z') do set "pb.cr=%%a"
    if "%~1"=="" ( set "pb.fillChar=#" ) else ( set "pb.fillChar=%~1" )
    if "%~2"=="" ( set "pb.dotChar=." ) else ( set "pb.dotChar=%~2" )
    set "pb.console.columns="
    for /f "tokens=2 skip=4" %%f in ('mode con') do if not defined pb.console.columns set "pb.console.columns=%%f"
    set /a "pb.barArea=pb.console.columns/2-2", "pb.textArea=pb.barArea-9"
    set "pb.fill="
    setlocal enableextensions enabledelayedexpansion
    for /l %%p in (1 1 %pb.barArea%) do set "pb.fill=!pb.fill!%pb.fillChar%"
    set "pb.fill=!pb.fill:~0,%pb.barArea%!"
    set "pb.dots=!pb.fill:%pb.fillChar%=%pb.dotChar%!"
    set "pb.back=!pb.fill:~0,%pb.textArea%!"
    set "pb.back=!pb.back:%pb.fillChar%= !"
    endlocal & set "pb.fill=%pb.fill%" & set "pb.dots=%pb.dots%" & set "pb.back=%pb.back%"
    goto :eof

:finalizeProgressBar [erase]
    if defined pb.cr (
        if not "%~1"=="" (
            setlocal enabledelayedexpansion
            set "pb.back="
            for /l %%p in (1 1 %pb.console.columns%) do set "pb.back=!pb.back! "
            <nul set /p "pb.prompt=!pb.cr!!pb.back:~1!!pb.cr!"
            endlocal
        )
    )
    for /f "tokens=1 delims==" %%v in ('set pb.') do set "%%v="
    goto :eof

:: -------------------------- 第二个进度条子例程(脚本整体进度) --------------------------
:drawProgressBar1 value [text]
    if "%~1"=="" goto :eof
    if not defined pb1.barArea call :initProgressBar1
    setlocal enableextensions enabledelayedexpansion
    set /a "pb1.value=%~1 %% 101", "pb1.filled=pb1.value*pb1.barArea/100", "pb1.dotted=pb1.barArea-pb1.filled", "pb1.pct=1000+pb1.value"
    set "pb1.pct=%pb1.pct:~-3%"
    if "%~2"=="" ( set "pb1.text=" ) else ( 
        set "pb1.text=%~2%pb1.back%" 
        set "pb1.text=!pb1.text:~0,%pb1.textArea%!"
    )
    <nul set /p "pb1.prompt=[!pb1.fill:~0,%pb1.filled%!!pb1.dots:~0,%pb1.dotted%!][ %pb1.pct% ] %pb1.text%!pb1.cr!"
    endlocal
    goto :eof

:initProgressBar1 [fillChar] [dotChar]
    if defined pb1.cr call :finalizeProgressBar1
    for /f %%a in ('copy "%~f0" nul /z') do set "pb1.cr=%%a"
    if "%~1"=="" ( set "pb1.fillChar=#" ) else ( set "pb1.fillChar=%~1" )
    if "%~2"=="" ( set "pb1.dotChar=." ) else ( set "pb1.dotChar=%~2" )
    set "pb1.console.columns="
    for /f "tokens=2 skip=4" %%f in ('mode con') do if not defined pb1.console.columns set "pb1.console.columns=%%f"
    set /a "pb1.barArea=pb1.console.columns/2-2", "pb1.textArea=pb1.barArea-9"
    set "pb1.fill="
    setlocal enableextensions enabledelayedexpansion
    for /l %%p in (1 1 %pb1.barArea%) do set "pb1.fill=!pb1.fill!%pb1.fillChar%"
    set "pb1.fill=!pb1.fill:~0,%pb1.barArea%!"
    set "pb1.dots=!pb1.fill:%pb1.fillChar%=%pb1.dotChar%!"
    set "pb1.back=!pb1.fill:~0,%pb1.textArea%!"
    set "pb1.back=!pb1.back:%pb1.fillChar%= !"
    endlocal & set "pb1.fill=%pb1.fill%" & set "pb1.dots=%pb1.dots%" & set "pb1.back=%pb1.back%"
    goto :eof

:finalizeProgressBar1 [erase]
    if defined pb1.cr (
        if not "%~1"=="" (
            setlocal enabledelayedexpansion
            set "pb1.back="
            for /l %%p in (1 1 %pb1.console.columns%) do set "pb1.back=!pb1.back! "
            <nul set /p "pb1.prompt=!pb1.cr!!pb1.back:~1!!pb1.cr!"
            endlocal
        )
    )
    for /f "tokens=1 delims==" %%v in ('set pb1.') do set "%%v="
    goto :eof

关键细节说明

  1. ANSI光标控制:!ESC!1B表示光标下移一行,!ESC!1A表示光标上移一行,通过这两个命令实现两个进度条的切换更新
  2. 变量隔离:第二组子例程的所有变量都以pb1.开头,完全独立于第一组的pb.变量,避免互相干扰
  3. 进度条初始化:分别调用:initProgressBar和:initProgressBar1可以设置不同的填充字符,区分两个进度条
  4. 清理逻辑:结束时需要分别调用:finalizeProgressBar 1和:finalizeProgressBar1 1来清除两行进度条

内容的提问来源于stack exchange,提问作者Josh T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 21:20:43