【bat】バッチファイルでプログレスバーを表示する方法完全ガイド|行内上書き・パーセンテージ・ファイル処理連動・スピナーアニメーションまで徹底解説

バッチファイルで大量ファイルのコピーやDB処理を実行する際、画面に何も表示されないと「処理が止まったのか」「あとどれくらいかかるのか」が分からず不安になります。GUIのプログレスバーは実装できませんが、工夫次第でそれに近い進捗表示を実現できます

本記事では、シンプルな文字バーからキャリッジリターンを使った行内上書き・ファイル処理との連動・スピナーアニメーションまで、段階的に解説します。

この記事で分かること

  • バッチファイルでの進捗表示における2つのアプローチ(cls方式 vs 行内上書き方式)の違い
  • キャリッジリターン(CR)を使って画面をちらつかせずに進捗を更新する方法
  • バー文字+パーセンテージの組み合わせ表示
  • for /r ループでのファイル処理と進捗表示の連動
  • スピナーアニメーションとマルチフェーズ対応の実践テンプレート
スポンサーリンク

2つのアプローチの比較

バッチファイルで進捗を更新する方法は大きく2種類あります。どちらを選ぶかで UX が大きく変わります。

方式 仕組み ちらつき 実装難度 用途
cls方式 画面全体をクリアして再描画 あり(画面が一瞬消える) 簡単 ステップ数が少ない・デバッグ用
行内上書き方式(CR) キャリッジリターンで行頭に戻って上書き なし やや複雑 本番運用・長時間処理
どちらを使うべきか:ちらつきが気になる場合や長時間処理では行内上書き方式を使います。シンプルに試すだけなら cls方式で十分です。本記事では両方を実装例付きで解説します。

方法①:cls を使ったシンプルな進捗更新

最も簡単な方法です。ループごとにclsで画面を消してから進捗を表示します。

progress_cls.bat
@echo off
setlocal enabledelayedexpansion

set MAX=20
set "BAR="

for /L %%i in (1,1,%MAX%) do (
    set /a PERCENT=%%i*100/%MAX%
    set "BAR=!BAR!#"
    cls
    echo 処理中... [!BAR!] !PERCENT!%%
    rem ここに実際の処理を書く
    timeout /t 1 >nul
)

cls
echo 完了!
pause
実行例(コンソール表示)
# ステップ 10/20 のときの画面イメージ

処理中... [##########..........] 50%

# cls のたびに画面がクリアされ、この1行だけ再表示される

欠点clsのたびに画面が一瞬真っ暗になります。処理速度が速いと激しくちらつくため、後述の行内上書き方式の方が見た目が良好です。

方法②:キャリッジリターンで行内上書き(推奨)

Windowsコマンドプロンプトではキャリッジリターン(CR, 文字コード0x0D)を出力すると、カーソルが行頭に戻ります。この性質を使えば、同じ行を何度も上書きでき画面ちらつきがゼロになります。

CR文字の取得方法

バッチファイルでCR文字を変数に入れるにはcopy /zコマンドを利用するトリックを使います。

CR文字の取得
rem copy /z は自己コピーで最後に CR を書き出す性質を利用
for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

rem CR変数を使って行内上書き
<nul set /p ="処理中...!CR!"

このCR変数をプログレスバー文字列の末尾に付けることで、次の更新時に同じ行を上書きします。

行内上書きによる基本的なプログレスバー

progress_cr.bat
@echo off
setlocal enabledelayedexpansion

rem CR文字を取得
for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

set MAX=30

for /L %%i in (1,1,%MAX%) do (
    set /a PERCENT=%%i*100/%MAX%
    rem 行内上書き: CR で行頭に戻す
    <nul set /p ="処理中... !PERCENT!%% (!CR!"
    rem ここに実際の処理を書く
    timeout /t 1 >nul
)

echo.
echo 完了!
pause
<nul set /p = の仕組みset /p は本来ユーザー入力を求めるコマンドですが、<nul(空入力)と組み合わせると改行なしでテキストを出力できます。これはバッチファイルで唯一「改行なし出力」ができるテクニックです。

方法③:バー文字+パーセンテージの組み合わせ

数値だけでなく視覚的な「棒グラフ」を表示すると、一目で進捗が分かります。バーの長さを動的に計算して表示します。

ASCII文字バー(#と.で表現)

progress_ascii_bar.bat
@echo off
setlocal enabledelayedexpansion

for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

set MAX=50
set BARLEN=20

for /L %%i in (1,1,%MAX%) do (
    set /a PERCENT=%%i*100/%MAX%
    set /a FILLED=%%i*%BARLEN%/%MAX%
    set /a EMPTY=%BARLEN%-!FILLED!

    rem バー文字列を構築
    set "BAR="
    for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!#"
    for /L %%j in (1,1,!EMPTY!)  do set "BAR=!BAR!."

    rem 行内上書きで表示(末尾スペースで前の文字を消す)
    <nul set /p ="[!BAR!] !PERCENT!%%   !CR!"
    timeout /t 1 >nul
)

echo.
echo [####################] 100%% - 完了!
pause
実行例(コンソール表示)
# 処理の進行に合わせて # が増え、. が減っていく

[####................]  20%
[##########..........]  50%
[################....]  80%
[####################] 100%
末尾スペースの重要性:行内上書きでは前回の文字が残ることがあります。 (スペース数個)を末尾に付けておくと、桁数が減ったときの残像を消せます。

全角ブロック文字バー(■と□で表現)

日本語環境であれば全角の記号文字を使うと見栄えが良くなります。ただし全角は半角2文字分の幅を取るため、バー幅の計算を半分にします。

progress_block_bar.bat
@echo off
setlocal enabledelayedexpansion

for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

set MAX=40
set BARLEN=10

for /L %%i in (1,1,%MAX%) do (
    set /a PERCENT=%%i*100/%MAX%
    set /a FILLED=%%i*%BARLEN%/%MAX%
    set /a EMPTY=%BARLEN%-!FILLED!

    set "BAR="
    for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!■"
    for /L %%j in (1,1,!EMPTY!)  do set "BAR=!BAR!□"

    <nul set /p ="進捗: [!BAR!] !PERCENT!%%   !CR!"
    timeout /t 1 >nul
)

echo.
echo 処理が完了しました。
pause
実行例(コンソール表示)
# ■ が埋まり □ が減っていくビジュアルイメージ

進捗: [■■□□□□□□□□]  20%
進捗: [■■■■■□□□□□]  50%
進捗: [■■■■■■■■□□]  80%
進捗: [■■■■■■■■■■] 100%
バー文字 文字種 見た目例 メモ
# / . 半角ASCII [#####…..] どの環境でも安全
= / - 半角ASCII [=====—–] シンプルで視認しやすい
■ / □ 全角記号 [■■■□□□] 日本語環境で見栄えが良い

方法④:スピナーアニメーション

処理件数が不明な場合や「実行中」を示すだけで良い場合は、回転するスピナー表示が適しています。

progress_spinner.bat
@echo off
setlocal enabledelayedexpansion

for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

rem スピナー文字の配列(インデックス 0〜3)
set "SPIN[0]=-"
set "SPIN[1]=\"
set "SPIN[2]=|"
set "SPIN[3]=/"

set IDX=0
set COUNT=0

:spinner_loop
rem ここに実際の処理を1単位書く
timeout /t 1 >nul
set /a COUNT+=1

rem スピナーを更新
<nul set /p ="処理中... !SPIN[%IDX%]! (%COUNT% 件処理済み)   !CR!"

set /a IDX=(IDX+1)%%4

rem 終了条件(例: 20件処理したら終了)
if %COUNT% lss 20 goto spinner_loop

echo.
echo 完了: %COUNT% 件処理しました。
pause
実行例(コンソール表示)
# - \ | / が順に切り替わりながら同じ行を上書き更新

処理中... -  ( 1 件処理済み)
処理中... \  ( 5 件処理済み)
処理中... |  (12 件処理済み)
処理中... /  (20 件処理済み)

完了: 20 件処理しました。

方法⑤:ファイル処理と進捗表示を連動させる

実務では「フォルダ内のファイルを1件ずつ処理しながら進捗を表示したい」というケースが多いです。ただしバッチファイルはファイルの総数を事前にカウントしてからループする必要があります。

progress_files.bat
@echo off
setlocal enabledelayedexpansion

for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

set TARGET_DIR=C:\work\files
set BARLEN=20

rem ===== ステップ1: 総ファイル数をカウント =====
set TOTAL=0
for %%F in ("%TARGET_DIR%\*") do set /a TOTAL+=1

if %TOTAL% equ 0 (
    echo 対象ファイルがありません。
    exit /b 1
)

echo 対象ファイル数: %TOTAL% 件

rem ===== ステップ2: ファイルを処理しながら進捗表示 =====
set PROCESSED=0

for %%F in ("%TARGET_DIR%\*") do (
    rem 実際の処理(例: コピー)
    copy "%%F" "C:\work\output\" >nul 2>&1

    set /a PROCESSED+=1
    set /a PERCENT=PROCESSED*100/TOTAL
    set /a FILLED=PROCESSED*%BARLEN%/TOTAL
    set /a EMPTY=%BARLEN%-FILLED

    set "BAR="
    for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!#"
    for /L %%j in (1,1,!EMPTY!)  do set "BAR=!BAR!."

    <nul set /p ="[!BAR!] !PERCENT!%% (!PROCESSED!/%TOTAL%) %%~nxF   !CR!"
)

echo.
echo 処理完了: %PROCESSED% 件
pause
実行例(コンソール表示)
対象ファイル数: 8 件

[#####...............] 25% (2/8) document_A.txt
[##########..........] 50% (4/8) document_B.xlsx
[###############.....] 75% (6/8) image_001.png
[####################] 100% (8/8) report_final.pdf

処理完了: 8 件
ネストしたforループと遅延展開の注意:バー文字列を構築するfor /L %%jループを外側のループ内に入れる場合、!BAR!の遅延展開が必要です。setlocal enabledelayedexpansionを忘れないようにしてください。

方法⑥:マルチフェーズ対応(複数段階の進捗表示)

「バックアップ→圧縮→転送」のように処理が複数段階に分かれる場合は、フェーズ名も合わせて表示すると分かりやすくなります。

progress_multiphase.bat
@echo off
setlocal enabledelayedexpansion

for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

rem フェーズ定義
set TOTAL_PHASES=3
set PHASE=0

call :run_phase "バックアップ" 10
call :run_phase "圧縮処理" 5
call :run_phase "ファイル転送" 8

echo.
echo ===== すべての処理が完了しました =====
pause
exit /b 0

:run_phase
set "PHASE_NAME=%~1"
set STEPS=%~2
set /a PHASE+=1

echo.
echo [フェーズ %PHASE%/%TOTAL_PHASES%] %PHASE_NAME%

for /L %%i in (1,1,%STEPS%) do (
    set /a PERCENT=%%i*100/%STEPS%
    set /a FILLED=%%i*20/%STEPS%
    set /a EMPTY=20-!FILLED!
    set "BAR="
    for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!#"
    for /L %%j in (1,1,!EMPTY!)  do set "BAR=!BAR!."
    <nul set /p ="  [!BAR!] !PERCENT!%%   !CR!"
    rem ここに実際の処理を書く
    timeout /t 1 >nul
)
<nul set /p ="  [####################] 100%%   "
echo. & echo   完了
exit /b 0
実行例(コンソール表示)
[フェーズ 1/3] バックアップ
  [####################] 100%
  完了

[フェーズ 2/3] 圧縮処理
  [##########..........] 50%

[フェーズ 3/3] ファイル転送
  [####################] 100%
  完了

===== すべての処理が完了しました =====

よくある落とし穴と対処法

落とし穴①:ループ内でバー構築ループを使うと遅い

バー文字列をfor /Lで1文字ずつ積み上げると、ステップ数が多い場合にループ内でのループ実行が遅くなります。ステップ数が多いときはバーの長さを少なくするか、バー構築を省いてパーセンテージのみ表示するのが実用的です。

NG: ステップ数が多いと重い
rem MAX=1000 のようにステップが多いと
rem 各ステップで1000回ループするため非常に遅くなる
set BARLEN=1000
for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!#"
OK: バー長を固定で短くする
rem バーは最大20文字に固定
rem ステップ数に関わらずバー構築ループは最大20回
set BARLEN=20
set /a FILLED=CURRENT*%BARLEN%/TOTAL

落とし穴②:遅延展開が有効でない

NG: 遅延展開なしでループ内変数が更新されない
rem setlocal enabledelayedexpansion がないと
rem ループ内で !BAR! が空のまま
@echo off
set "BAR="
for /L %%i in (1,1,5) do (
    set "BAR=%BAR%#"
    echo [%BAR%]     <- 常に空になる
)
OK: setlocal enabledelayedexpansion を追加
@echo off
setlocal enabledelayedexpansion
set "BAR="
for /L %%i in (1,1,5) do (
    set "BAR=!BAR!#"
    echo [!BAR!]     <- 正しく更新される
)

落とし穴③:ログファイルと進捗表示の競合

進捗表示をしながらログファイルにも書き出す場合、>> log.txtのリダイレクトを間違えると進捗表示がログに混入します。

ログと進捗表示を分離
rem 進捗表示はコンソールのみ(ログに残さない)
<nul set /p ="[!BAR!] !PERCENT!%%   !CR!"

rem ログには処理結果のみ書く
echo %%F を処理完了 >> "%LOG_FILE%"

rem 両方に出力したい場合は明示的に分ける
echo 処理完了: %%F
echo 処理完了: %%F >> "%LOG_FILE%"

完全な実践テンプレート

これまでの内容を統合した、そのまま業務で使えるテンプレートです。行内上書き・バー表示・ファイル処理連動・ログ出力をすべて含みます。

progress_complete.bat
@echo off
setlocal enabledelayedexpansion

rem ===== 設定 =====
set TARGET_DIR=C:\work\input
set OUTPUT_DIR=C:\work\output
set LOG_FILE=C:\work\progress_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log
set BARLEN=25

rem ===== 初期化 =====
for /f %%a in ('copy /z "%~f0" nul') do set "CR=%%a"

if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"

echo ===== 処理開始: %DATE% %TIME% ===== > "%LOG_FILE%"

rem ===== 総数カウント =====
set TOTAL=0
for %%F in ("%TARGET_DIR%\*.*") do set /a TOTAL+=1

if %TOTAL% equ 0 (
    echo [ERROR] 対象ファイルがありません: %TARGET_DIR%
    exit /b 1
)

echo 対象: %TOTAL% 件のファイルを処理します。
echo 対象: %TOTAL% 件 >> "%LOG_FILE%"

rem ===== メイン処理ループ =====
set PROCESSED=0
set ERRORS=0

for %%F in ("%TARGET_DIR%\*.*") do (
    rem ---- 実際の処理 ----
    copy "%%F" "%OUTPUT_DIR%\" >nul 2>&1

    if !ERRORLEVEL! equ 0 (
        set /a PROCESSED+=1
        echo [OK] %%~nxF >> "%LOG_FILE%"
    ) else (
        set /a ERRORS+=1
        echo [ERROR] %%~nxF >> "%LOG_FILE%"
    )

    rem ---- 進捗バーを更新 ----
    set /a DONE=PROCESSED+ERRORS
    set /a PERCENT=DONE*100/TOTAL
    set /a FILLED=DONE*%BARLEN%/TOTAL
    set /a EMPTY=%BARLEN%-!FILLED!

    set "BAR="
    for /L %%j in (1,1,!FILLED!) do set "BAR=!BAR!#"
    for /L %%j in (1,1,!EMPTY!)  do set "BAR=!BAR!."

    <nul set /p ="[!BAR!] !PERCENT!%% (!DONE!/%TOTAL%) %%~nxF   !CR!"
)

rem ===== 完了メッセージ =====
echo.
echo ===========================
echo 完了: %PROCESSED% 件成功 / %ERRORS% 件失敗
echo ===========================
echo 完了: %PROCESSED% 件成功 / %ERRORS% 件失敗 >> "%LOG_FILE%"
pause

関連記事

よくある質問

Q. キャリッジリターン(CR)の取得に使う copy /z "%~f0" nul はどういう仕組みですか?
A. copy /z はバイナリコピーモードで最後にCR(0x0D)を書き出す動作があります。for /f でその出力を変数に取り込むことで、CR文字を変数に格納できます。"%~f0"は実行中のバッチファイル自身のパスです。この自己参照は副作用のないお決まりのパターンとして広く使われています。
Q. ファイル総数の事前カウントをせずに進捗を表示することはできますか?
A. 総数が不明な場合はスピナー(方法④)や「X件処理済み」という形式で表示します。パーセンテージには計算のために総数が必要なので、どうしてもパーセンテージを出したい場合は事前にfor %%F in (*) do set /a TOTAL+=1でカウントするひと手間が必要です。
Q. プログレスバーの表示を PowerShell と組み合わせることはできますか?
A. はい。PowerShellのWrite-Progressコマンドレットを使うとGUIに近いプログレスバーが表示できます。バッチファイルからpowershell -Command "Write-Progress -Activity '処理中' -PercentComplete 50"のように呼び出せますが、PowerShellの起動コストがあるためループ内での頻繁な呼び出しは避け、フェーズの切り替えタイミングで使うのが現実的です。
Q. バーの文字に | を使ったら表示が崩れました。なぜですか?
A. |はバッチファイルでパイプ演算子として解釈されるため、変数の値に含めると予期しない動作になります。#=*などの文字を使ってください。同様に&><も特殊文字なので避けます。
Q. 進捗バーを表示しながら標準エラー出力(stderr)も抑制したい場合は?
A. ループ内の個別コマンドに2>nulを付けて標準エラーを捨てます。バッチファイル全体のエラー出力を抑制したい場合は2>"エラーログファイル"でエラーログに誘導するのが安全です。@echo offはコマンド自体のエコーを止めるものでエラー出力には影響しません。詳しくは画面出力最適化の記事を参照してください。