【bat】バッチファイルでフラグを更新する方法完全ガイド|変数フラグ・ファイルフラグ・ループ内制御・バッチ間連携・実践パターンまで

バッチファイルで「処理済みかどうか」「エラーが起きたかどうか」を記録するには、フラグ(flag)を使います。フラグとは状態を0/1などの値で管理する変数のことで、条件分岐やループ制御・バッチ間連携に欠かせないテクニックです。

この記事では、バッチファイルでフラグを設定・更新する方法を基礎から実務パターンまで体系的に解説します。

この記事でわかること

  • 変数フラグの基本的な設定・更新・トグル切り替え
  • ループ内でフラグを更新する方法(遅延展開の必要性)
  • 複数フラグを使った複雑な状態管理
  • ファイルフラグによる永続化とバッチ間連携
  • フラグ管理でよくある落とし穴5選と対策
  • 実践例3本・FAQ6問
スポンサーリンク

1. フラグの基本|変数フラグの設定と更新

最もシンプルなフラグは set コマンドで変数に 0 または 1 を代入する方法です。

基本形:初期化→条件更新→参照

@echo off
setlocal

:: ── フラグ初期化 ──
set ERROR_FLAG=0
set DONE_FLAG=0

:: ── 処理実行(ここでは例として xcopy) ──
xcopy /y "C:\src\*" "D:\backup\" >nul 2>&1
if errorlevel 1 (
    set ERROR_FLAG=1
) else (
    set DONE_FLAG=1
)

:: ── フラグで後続処理を分岐 ──
if "%ERROR_FLAG%"=="1" (
    echo [ERROR] コピーに失敗しました
    exit /b 1
)
if "%DONE_FLAG%"=="1" (
    echo [OK] コピーが完了しました
)

ポイントは「フラグを最初に初期化(0)してから、条件に応じて更新する」流れです。条件分岐の書き方と合わせて覚えておきましょう。

フラグの値の種類

フラグ値 用途
0 / 1 オン/オフ、成功/失敗 ERROR_FLAG=1
文字列 状態名をそのまま記録 STATUS=success
空文字 未設定を空で表す FLAG=(空)

数値フラグ(0/1)が最も一般的です。文字列フラグは可読性が上がりますが、比較時に大文字小文字の差異に注意が必要です。

2. フラグのトグル切り替え(0⇔1)

実行のたびに 0 と 1 を交互に切り替えたい場合のパターンです。

if 文による切り替え

@echo off
setlocal

set FLAG=0

if "%FLAG%"=="0" (
    set FLAG=1
) else (
    set FLAG=0
)

echo フラグの現在値: %FLAG%

算術演算による切り替え(XOR)

@echo off
setlocal

set FLAG=0

:: SET /A で XOR 演算(0→1、1→0)
set /a FLAG=FLAG^^1

echo フラグ: %FLAG%

set /a FLAG=FLAG^^1 は XOR 演算で 0 と 1 を交互に切り替えます。^^^ のエスケープです。環境変数の設定と参照方法も参照してください。

3. ループ内でフラグを更新する方法

for ループや if ブロック内でフラグを更新して同じブロック内で参照するには、遅延展開(setlocal enabledelayedexpansion)が必須です。

NG例:遅延展開なしの失敗パターン

@echo off
setlocal

set FOUND=0

for %%F in (*.txt) do (
    set FOUND=1
    :: NG: %FOUND% は常に 0(ループ開始時の値)
    echo %FOUND%
)

OK例:遅延展開ありの正しいパターン

@echo off
setlocal enabledelayedexpansion

set FOUND=0

for %%F in (*.txt) do (
    set FOUND=1
    :: OK: !FOUND! でループ内の最新値を参照
    echo !FOUND!
)

if "!FOUND!"=="1" (
    echo txt ファイルが見つかりました
) else (
    echo txt ファイルはありません
)

遅延展開については setlocal enabledelayedexpansion 完全ガイドで詳しく解説しています。

ループ内でフラグを立てて早期 break する

バッチに break 文はないため、goto でループを抜けます。

@echo off
setlocal enabledelayedexpansion

set TARGET=report.csv
set FOUND=0

for %%F in (*.csv) do (
    if "%%F"=="%TARGET%" (
        set FOUND=1
        goto :found
    )
)

:found
if "%FOUND%"=="1" (
    echo %TARGET% が見つかりました
) else (
    echo %TARGET% は見つかりませんでした
)

4. 複数フラグを使った状態管理

「成功・失敗・スキップ」など複数の状態を管理するパターンです。

@echo off
setlocal enabledelayedexpansion

:: 処理結果フラグを初期化
set CNT_SUCCESS=0
set CNT_ERROR=0
set CNT_SKIP=0

for %%F in (*.log) do (
    :: ファイルサイズが 0 なら skip フラグを立てる
    set "SKIP="
    for %%S in ("%%F") do if %%~zS==0 set SKIP=1

    if defined SKIP (
        :: サイズ0はスキップ
        set /a CNT_SKIP+=1
    ) else (
        :: 処理本体(例: コピー)
        copy "%%F" "D:\archive\" >nul 2>&1
        if !errorlevel!==0 (
            set /a CNT_SUCCESS+=1
        ) else (
            set /a CNT_ERROR+=1
        )
    )
)

echo ===== 処理結果 =====
echo 成功: !CNT_SUCCESS! 件
echo 失敗: !CNT_ERROR! 件
echo スキップ: !CNT_SKIP! 件

if !CNT_ERROR! GTR 0 exit /b 1

set /a CNT_SUCCESS+=1 で数値フラグのカウントアップができます。数値比較の方法も合わせて確認してください。

5. ファイルフラグ|バッチ間連携と永続化

変数フラグはバッチが終了すると消えます。再起動やバッチ間でフラグを引き継ぐにはファイルフラグを使います。

5-1. ファイルの存在でフラグを表す

ファイルが「ある=ON」「ない=OFF」というシンプルな方法です。

@echo off
setlocal

set FLAG_FILE=C:\work\done.flag

:: ── フラグチェック ──
if exist "%FLAG_FILE%" (
    echo 前回処理済みです。スキップします。
    exit /b 0
)

:: ── メイン処理 ──
echo 処理を実行中...
xcopy /y "C:\src\*" "D:\dst\" >nul

:: ── 完了フラグを立てる ──
echo %date% %time% > "%FLAG_FILE%"
echo 処理完了。フラグを作成しました。

ファイル存在チェックの詳細は IF EXISTでファイル・フォルダの存在確認をする方法を参照してください。

5-2. ファイルの内容でフラグを管理する

フラグの値(0/1・文字列)をファイルに書き込み、次回読み込む方法です。

@echo off
setlocal

set FLAG_FILE=C:\work\status.flag

:: ── フラグ読み込み(ファイルがなければ初期値0) ──
set FLAG=0
if exist "%FLAG_FILE%" (
    set /p FLAG=<%FLAG_FILE%
)
echo 読み込んだフラグ: %FLAG%

:: ── 処理後にフラグを更新して書き込み ──
set FLAG=1
echo %FLAG%> "%FLAG_FILE%"
echo フラグを %FLAG% に更新しました
注意:echo %FLAG%> "%FLAG_FILE%"> の前に半角スペースを入れないこと。入れると末尾に余分なスペースが入り、次回の比較が失敗します。

5-3. バッチ間でフラグを受け渡す

親バッチが子バッチの完了を待つ連携パターンです。外部ファイルのフラグを読み込んで処理を分岐する方法も参考にしてください。

:: ====== master.bat(親バッチ)======
@echo off
setlocal

set FLAG_FILE=C:\work\worker_done.flag

:: フラグをリセット
if exist "%FLAG_FILE%" del "%FLAG_FILE%"

:: 子バッチを非同期で起動
start "" cmd /c worker.bat

:: 子バッチの完了フラグを待機(最大60秒)
set TIMEOUT_SEC=60
set ELAPSED=0
:wait_loop
if exist "%FLAG_FILE%" goto :done
timeout /t 2 /nobreak >nul
set /a ELAPSED+=2
if %ELAPSED% GEQ %TIMEOUT_SEC% (
    echo [TIMEOUT] 子バッチが時間内に完了しませんでした
    exit /b 1
)
goto :wait_loop

:done
echo [OK] 子バッチの処理が完了しました
:: ====== worker.bat(子バッチ)======
@echo off
setlocal

set FLAG_FILE=C:\work\worker_done.flag

:: 処理本体
echo 処理実行中...
timeout /t 5 /nobreak >nul

:: 完了フラグを書き込む
echo done> "%FLAG_FILE%"
echo フラグを立てました: %FLAG_FILE%

6. 落とし穴5選と対策

落とし穴1:ループ内で %VAR% を使うと古い値が取れる

:: NG: for ブロック内の %FLAG% は常にループ開始時の値
for %%F in (*) do (
    set FLAG=1
    echo %FLAG%  :: 常に初期値(例: 0)
)

:: OK: !FLAG! で最新値を取得
setlocal enabledelayedexpansion
for %%F in (*) do (
    set FLAG=1
    echo !FLAG!  :: 更新後の値(1)
)

落とし穴2:フラグ変数の比較で引用符を忘れる

:: NG: FLAG が未定義だと "==" が構文エラー
if %FLAG%==1 echo ON

:: OK: 必ず引用符で囲む
if "%FLAG%"=="1" echo ON

:: OK: 空文字も安全に比較できる
if "%FLAG%"=="" echo フラグ未設定

落とし穴3:echo でフラグファイルに末尾スペースが混入する

:: NG: echo の後スペース+リダイレクト → ファイルに "1 " が入る
echo %FLAG% > flag.txt

:: OK: > の直前にスペースを入れない
echo %FLAG%> flag.txt

:: 確認方法
set /p READ_FLAG=<flag.txt
echo [%READ_FLAG%]  :: [1] ならOK、[1 ] ならNG

末尾スペースの問題については 変数の末尾に余計な空白が入るときの原因と対策も参照してください。

落とし穴4:setlocal のスコープ外でフラグが消える

@echo off
setlocal
set FLAG=1
endlocal

:: NG: endlocal 後は FLAG が消える
echo %FLAG%  :: 空文字

:: OK: endlocal と同時に値を引き継ぐ
setlocal
set FLAG=1
endlocal & set FLAG=%FLAG%
echo %FLAG%  :: 1

落とし穴5:フラグファイルのパスにスペースがある

:: NG: パスにスペースがあると set /p が失敗
set FLAG_FILE=C:\my work\status.flag
set /p FLAG=<%FLAG_FILE%

:: OK: 引用符で囲む
set FLAG_FILE=C:\my work\status.flag
set /p FLAG=<"%FLAG_FILE%"

:: フラグファイルは空白のないパスに置くのが最善
set FLAG_FILE=C:\work\status.flag

7. 実践例3本

実践例1:バックアップの成否フラグを記録してリトライ制御

@echo off
setlocal enabledelayedexpansion

set SRC=C:\data
set DST=D:\backup
set LOG=C:\work\backup.log
set MAX_RETRY=3
set RETRY=0
set SUCCESS=0

:retry_loop
xcopy /e /y "%SRC%\*" "%DST%\" >> "%LOG%" 2>&1
if errorlevel 1 (
    set /a RETRY+=1
    echo [RETRY %RETRY%/%MAX_RETRY%] バックアップ失敗
    if !RETRY! LSS !MAX_RETRY! (
        timeout /t 10 /nobreak >nul
        goto :retry_loop
    )
    echo [ERROR] %MAX_RETRY% 回試行しましたが失敗しました >> "%LOG%"
    exit /b 1
) else (
    set SUCCESS=1
)

if "!SUCCESS!"=="1" (
    echo %date% %time% バックアップ完了 >> "%LOG%"
    echo [OK] バックアップが完了しました
)

実践例2:日次バッチで「本日実行済み」フラグを管理

@echo off
setlocal

:: 本日の日付でフラグファイル名を生成
set TODAY=%date:~0,4%%date:~5,2%%date:~8,2%
set FLAG_FILE=C:\work\daily_%TODAY%.flag

:: 実行済みチェック
if exist "%FLAG_FILE%" (
    echo [SKIP] 本日(%TODAY%)はすでに実行済みです
    exit /b 0
)

:: メイン処理
echo [RUN] 日次バッチ処理を実行します...
call :main_process
if errorlevel 1 (
    echo [ERROR] 処理に失敗しました。フラグは立てません。
    exit /b 1
)

:: 成功時のみフラグを立てる
echo %date% %time%> "%FLAG_FILE%"
echo [OK] 処理完了。フラグを作成: %FLAG_FILE%
exit /b 0

:main_process
:: ここに実際の処理を記述
echo 処理を実行中...
exit /b 0

日付の取り扱いについては 日付と時間をファイル名に挿入する方法も参照してください。

実践例3:マルチステップ処理の進捗フラグ管理

複数のステップがある処理で、途中で失敗した場合に次回は続きから実行する仕組みです。

@echo off
setlocal

set PROGRESS_FILE=C:\work\progress.flag
set STEP=0

:: 前回の進捗を読み込み
if exist "%PROGRESS_FILE%" (
    set /p STEP=<"%PROGRESS_FILE%"
    echo 前回の進捗から再開: STEP=%STEP%
)

:: STEP 1: データ取得
if %STEP% LSS 1 (
    echo [STEP1] データ取得中...
    call :fetch_data
    if errorlevel 1 ( echo STEP1 失敗 & exit /b 1 )
    set STEP=1
    echo %STEP%> "%PROGRESS_FILE%"
)

:: STEP 2: データ変換
if %STEP% LSS 2 (
    echo [STEP2] データ変換中...
    call :transform_data
    if errorlevel 1 ( echo STEP2 失敗 & exit /b 1 )
    set STEP=2
    echo %STEP%> "%PROGRESS_FILE%"
)

:: STEP 3: アップロード
if %STEP% LSS 3 (
    echo [STEP3] アップロード中...
    call :upload_data
    if errorlevel 1 ( echo STEP3 失敗 & exit /b 1 )
    set STEP=3
    echo %STEP%> "%PROGRESS_FILE%"
)

:: 完了: 進捗ファイルを削除
del "%PROGRESS_FILE%" >nul 2>&1
echo [完了] 全ステップが正常に完了しました
exit /b 0

:fetch_data
exit /b 0
:transform_data
exit /b 0
:upload_data
exit /b 0

前回の状態を引き継ぐ仕組みについては 前回処理の状態を記録して次回に引き継ぐ方法も参考にしてください。

8. まとめ

バッチファイルでフラグを更新する方法をまとめると以下のとおりです。

場面 推奨手法
バッチ内の状態管理 変数フラグ(set FLAG=0/1)
for/if ブロック内での更新・参照 遅延展開(!FLAG!)必須
再起動やバッチ間での引き継ぎ ファイルフラグ(存在/内容)
複数状態の管理 複数変数フラグ+set /a カウント
非同期バッチ間連携 ファイル存在フラグ+待機ループ

FAQ

Q. for ループ内でフラグを更新しても値が変わりません。なぜですか?
A. %VAR% はループ開始時に展開される仕様のため、ループ内で更新した値を参照するには setlocal enabledelayedexpansion!VAR! が必要です。setlocal enabledelayedexpansion 完全ガイドを参照してください。
Q. フラグファイルから読み込んだ値を比較すると失敗します。
A. echo FLAG> file の書き方でスペースや改行が混入することがあります。set /p で読んだ後に echo [%FLAG%] と囲んで確認し、余分な文字がないか確認してください。
Q. フラグを 0/1 の数値で管理するか、文字列(success/error)で管理するかどちらがよいですか?
A. バッチ内完結なら 0/1 が軽量で比較も単純です。ログやファイルに記録して人が読む用途には文字列が向いています。ただし文字列比較は大文字小文字の違いに注意してください(バッチは大文字小文字を区別しません)。
Q. バッチが異常終了したときにフラグファイルが残ってしまいます。対策は?
A. 処理の最初にフラグファイルの有無をチェックし「前回の異常終了からのリカバリ」として扱うか、完了時のみフラグを作成する設計にすることで対応できます。実践例2の「成功時のみフラグを立てる」パターンが参考になります。
Q. 複数の子バッチの完了をまとめて待つにはどうすれば良いですか?
A. 各子バッチに個別のフラグファイルを持たせ、親バッチで全フラグが揃うまでループする方法があります。ファイルの存在を監視するバッチファイル完全ガイドの複数ファイルAND監視パターンを参照してください。
Q. フラグのオン/オフを確認しながらデバッグするには?
A. @echo on にするか、各フラグ設定後に echo [DEBUG] FLAG=%FLAG% を挿入して確認します。変数展開の問題がある場合は echo [%FLAG%] と角括弧で囲むと空文字かどうかが分かりやすくなります。