如何在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
关键细节说明
- ANSI光标控制:
!ESC!1B表示光标下移一行,!ESC!1A表示光标上移一行,通过这两个命令实现两个进度条的切换更新 - 变量隔离:第二组子例程的所有变量都以
pb1.开头,完全独立于第一组的pb.变量,避免互相干扰 - 进度条初始化:分别调用
:initProgressBar和:initProgressBar1可以设置不同的填充字符,区分两个进度条 - 清理逻辑:结束时需要分别调用
:finalizeProgressBar 1和:finalizeProgressBar1 1来清除两行进度条
内容的提问来源于stack exchange,提问作者Josh T
相关产品推荐
相关产品推荐

