バッチファイルで複数のジョブを連携させるとき、「前のバッチが正常完了したかどうか」を外部ファイル経由で伝えるフラグ管理は実務でよく使われるテクニックです。本記事では、フラグファイルの設計パターンから読み込み・分岐・待機ループ・書き込み・エラーハンドリングまでを体系的に解説します。
- 外部ファイルのフラグを読み込んで処理を分岐する基本パターン
- 複数フラグを1ファイルで管理する方法
- フラグが特定値になるまでループで待機する方法(タイムアウト付き)
- フラグファイルをバッチから書き込み・更新する方法
- FOR 内で変数が更新されない問題(遅延展開)の対処法
- ファイルが存在しない場合のエラーハンドリング
フラグファイルを使う場面
バッチファイル間でステータスを共有したいケースは多くあります。
| 用途 | フラグの例 |
|---|---|
| 前処理バッチの完了を後続バッチが待つ | STATUS=DONE になるまでポーリング |
| 処理の一時停止・再開制御 | PAUSE=1 の間はループで待機 |
| 処理モードの切り替え | MODE=TEST / MODE=PROD で分岐 |
| 障害時の処理スキップ | SKIP_STEP2=1 で特定ステップを飛ばす |
| 外部ツール(VBA・Python 等)からの指示受け渡し | テキストファイルにフラグを書き込んで bat に伝える |
フラグファイルの設計パターン
パターン1: 値のみ(シンプル・単一フラグ)
REM flag.txt の内容(1行目に値のみ) 1
パターン2: キー=値形式(複数フラグを1ファイルで管理)
REM config.txt の内容 STATUS=DONE MODE=PROD RETRY_COUNT=3
パターン3: ファイルの存在自体をフラグにする
REM done.flag ファイルが存在すれば「完了」、なければ「未完了」
if exist done.flag (
echo 完了済みです
) else (
echo まだ完了していません
)
単純な完了フラグなら「ファイルの存在有無」を使うのが最もシンプルです。読み込み処理が不要で、競合も起きにくいです。複数の状態を管理したい場合はキー=値形式が適しています。
フラグファイルの読み込み方法
値のみのファイルを読み込む
@echo off
setlocal
REM flag.txt の1行目をそのまま変数に格納
set FLAG=
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
echo 読み込んだフラグ: %FLAG%
for /f ループ内で設定した変数を 同じループ内 で参照すると、%VAR% は常に空になります。ループ内で参照する場合は setlocal enabledelayedexpansion と !VAR! 記法を使ってください。詳細は setlocal enabledelayedexpansion 完全ガイド(FOR内変数・遅延展開) を参照してください。
キー=値形式のファイルからすべてのフラグを読み込む
@echo off
setlocal
REM config.txt の全キー=値ペアを変数に展開
for /f "usebackq tokens=1,* delims==" %%K in ("config.txt") do (
set %%K=%%L
)
REM 読み込み結果を確認
echo STATUS=%STATUS%
echo MODE=%MODE%
echo RETRY_COUNT=%RETRY_COUNT%
特定キーのみ読み込む
@echo off
setlocal
REM STATUS キーの値だけ取得する
set STATUS=
for /f "usebackq tokens=1,2 delims==" %%K in ("config.txt") do (
if /i "%%K"=="STATUS" set STATUS=%%L
)
echo STATUS=%STATUS%
フラグの値で処理を分岐する
基本的な3分岐
@echo off
setlocal
REM フラグを読み込む
set FLAG=
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
REM フラグの値で分岐
if "%FLAG%"=="1" (
echo フラグ=1: 処理Aを実行します
call :proc_a
) else if "%FLAG%"=="0" (
echo フラグ=0: 処理Bを実行します
call :proc_b
) else (
echo フラグ=%FLAG%: 想定外の値です
exit /b 1
)
exit /b 0
:proc_a
echo 処理A を実行中...
exit /b 0
:proc_b
echo 処理B を実行中...
exit /b 0
キー=値形式で読み込んだ複数フラグで分岐
@echo off
setlocal
REM config.txt を読み込む(STATUS と MODE を取得)
for /f "usebackq tokens=1,* delims==" %%K in ("config.txt") do set %%K=%%L
REM STATUS と MODE の組み合わせで分岐
if "%STATUS%"=="DONE" (
if "%MODE%"=="PROD" (
echo 本番モードで後続処理を開始します
goto :prod_process
) else if "%MODE%"=="TEST" (
echo テストモードで後続処理を開始します
goto :test_process
)
) else if "%STATUS%"=="ERROR" (
echo 前処理でエラーが発生しました。処理を中断します。
exit /b 1
) else (
echo 前処理が未完了です。STATUS=%STATUS%
exit /b 1
)
goto :EOF
:prod_process
echo 本番処理...
goto :EOF
:test_process
echo テスト処理...
goto :EOF
フラグが特定値になるまで待機するループ
基本的な待機ループ(タイムアウトなし)
@echo off
setlocal
:check_flag
REM フラグを毎回ファイルから再読み込み
set FLAG=
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
if "%FLAG%"=="1" (
echo フラグが 1 になりました。処理を開始します。
goto :main_process
)
echo フラグ=%FLAG% - 5秒後に再確認します...
timeout /t 5 /nobreak >nul
goto :check_flag
:main_process
echo メイン処理を実行中...
endlocal
タイムアウト付き待機ループ(実務推奨)
@echo off
setlocal enabledelayedexpansion
REM 設定値
set INTERVAL=5
set MAX_WAIT=300
set /a ELAPSED=0
:check_flag
REM フラグを再読み込み
set FLAG=
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
if "!FLAG!"=="DONE" (
echo [OK] フラグが DONE になりました。処理を開始します。
goto :main_process
)
REM タイムアウトチェック
set /a ELAPSED+=INTERVAL
if !ELAPSED! GEQ !MAX_WAIT! (
echo [ERROR] タイムアウト: %MAX_WAIT% 秒待機しましたが DONE になりませんでした。
exit /b 1
)
echo 待機中... 経過: !ELAPSED! 秒 / 上限: %MAX_WAIT% 秒
timeout /t %INTERVAL% /nobreak >nul
goto :check_flag
:main_process
echo メイン処理を実行中...
endlocal
フラグが永遠に更新されない障害が発生しても無限ループに陥らないよう、本番環境では必ず最大待機時間を設けてください。タイムアウト時は
exit /b 1 でエラー終了して監視ツールに検知させるのがベストプラクティスです。EXIT /B の使い方は EXIT /B 完全解説(戻り値・ウィンドウを閉じない仕組み) で詳しく解説しています。
フラグファイルを書き込み・更新する
シンプルな上書き
REM 値のみのフラグファイルを上書き(> は上書き、>> は追記)
echo 1 > flag.txt
REM 値の前後にスペースが入らないようにする場合
(echo 1)> flag.txt
REM キー=値形式でフラグファイルを書き込む
(
echo STATUS=DONE
echo MODE=PROD
echo UPDATED=%DATE% %TIME%
)> config.txt
既存ファイルの特定キーだけ更新する
@echo off
setlocal enabledelayedexpansion
REM config.txt の STATUS 行だけ書き換える
set TMPFILE=%TEMP%\config_tmp.txt
if exist "%TMPFILE%" del "%TMPFILE%"
for /f "usebackq tokens=1,* delims==" %%K in ("config.txt") do (
if /i "%%K"=="STATUS" (
echo STATUS=DONE>> "%TMPFILE%"
) else (
echo %%K=%%L>> "%TMPFILE%"
)
)
move /y "%TMPFILE%" config.txt >nul
echo STATUS を DONE に更新しました。
エラーハンドリング:ファイルが存在しない・読み込めない場合
@echo off
setlocal
REM ファイルの存在チェック
if not exist "flag.txt" (
echo [ERROR] flag.txt が見つかりません。
exit /b 1
)
REM フラグを読み込む
set FLAG=
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
REM 空だった場合のチェック(ファイルが空の場合)
if "%FLAG%"=="" (
echo [ERROR] flag.txt が空です。
exit /b 1
)
REM 想定値チェック
if not "%FLAG%"=="0" if not "%FLAG%"=="1" (
echo [ERROR] 想定外のフラグ値: %FLAG%
exit /b 1
)
echo フラグ=%FLAG%
endlocal
実践サンプル:バッチ間の連携制御
前処理バッチ(prepare.bat)と後続バッチ(main.bat)がフラグファイルで連携するパターンです。
前処理バッチ(prepare.bat)
@echo off
setlocal
echo 前処理開始
REM 開始時にフラグを RUNNING に設定
(echo STATUS=RUNNING)> status.txt
REM 実際の処理
call :do_prepare
if errorlevel 1 (
echo [ERROR] 前処理でエラーが発生しました。
(echo STATUS=ERROR)> status.txt
exit /b 1
)
REM 完了フラグを書き込む
(echo STATUS=DONE)> status.txt
echo 前処理完了。status.txt に DONE を書き込みました。
exit /b 0
:do_prepare
echo データの準備中...
REM 実際の処理をここに書く
exit /b 0
後続バッチ(main.bat)—タイムアウト付き待機
@echo off
setlocal enabledelayedexpansion
set INTERVAL=10
set MAX_WAIT=600
set /a ELAPSED=0
echo 前処理の完了を待機中...
:wait_loop
if not exist "status.txt" (
echo status.txt が存在しません。%INTERVAL% 秒後に再確認...
timeout /t %INTERVAL% /nobreak >nul
set /a ELAPSED+=INTERVAL
if !ELAPSED! GEQ !MAX_WAIT! goto :timeout_err
goto :wait_loop
)
set STATUS=
for /f "usebackq tokens=1,* delims==" %%K in ("status.txt") do (
if /i "%%K"=="STATUS" set STATUS=%%L
)
if "!STATUS!"=="DONE" goto :main_start
if "!STATUS!"=="ERROR" (
echo [ERROR] 前処理がエラー終了しました。処理を中断します。
exit /b 1
)
echo STATUS=!STATUS! - %INTERVAL% 秒後に再確認... (経過: !ELAPSED! 秒)
timeout /t %INTERVAL% /nobreak >nul
set /a ELAPSED+=INTERVAL
if !ELAPSED! GEQ !MAX_WAIT! goto :timeout_err
goto :wait_loop
:main_start
echo 前処理が完了しました。メイン処理を開始します。
REM メイン処理をここに書く
exit /b 0
:timeout_err
echo [ERROR] タイムアウト: %MAX_WAIT% 秒待機しましたが DONE になりませんでした。
exit /b 1
よくある落とし穴と対処法
落とし穴1: FOR 内で設定した変数を参照できない
REM NG: FOR ループ内で FLAG を設定してもすぐに参照できない
for /f "delims=" %%A in (flag.txt) do (
set FLAG=%%A
if "%FLAG%"=="1" echo NG: これは動かない
)
REM OK: 遅延展開を使う
setlocal enabledelayedexpansion
for /f "delims=" %%A in (flag.txt) do (
set FLAG=%%A
if "!FLAG!"=="1" echo OK: 遅延展開で参照できる
)
REM OK: goto で一度ループを抜けてから参照する
for /f "delims=" %%A in (flag.txt) do (set FLAG=%%A & goto :done)
:done
if "%FLAG%"=="1" echo OK: ループ外で参照する
落とし穴2: echo > でスペースが混入する
REM NG: echo 1 > flag.txt は "1 " と末尾にスペースが入る echo 1 > flag.txt REM OK: リダイレクト記号を直前に付ける(スペースなし) (echo 1)> flag.txt REM 確認方法 for /f "delims=" %%A in (flag.txt) do echo [%%A]
落とし穴3: フラグの比較で大文字・小文字を考慮する
REM NG: "done" と "DONE" を区別してしまう if "%STATUS%"=="DONE" echo 完了 REM OK: /i オプションで大文字・小文字を区別しない if /i "%STATUS%"=="done" echo 完了 (DONE でも done でも一致)
落とし穴4: 他プロセスがファイルを書き込み中に読むと空になる
REM 対策1: ファイルのコピーを作ってから読む
copy /y flag.txt flag_read.txt >nul 2>&1
if errorlevel 1 (
echo ファイルのコピーに失敗しました。再試行します。
timeout /t 1 /nobreak >nul
goto :check_flag
)
for /f "usebackq delims=" %%A in ("flag_read.txt") do set FLAG=%%A
REM 対策2: 読み込んだ値が空の場合は再試行する
if "%FLAG%"=="" (
timeout /t 1 /nobreak >nul
goto :check_flag
)
複数のバッチが同時に動作するケースでのエラーハンドリングの詳細は バッチファイルのログローテーション実装ガイド のログ管理パターンも参考にしてください。
よくある質問(FAQ)
スペースを含むパスは二重引用符で囲み、usebackq オプションを付けてください。
REM スペースを含まないパス
for /f "delims=" %%A in (C:\batch\flag.txt) do set FLAG=%%A
REM スペースを含むパス(usebackq でバッククォートをファイル名と認識)
for /f "usebackq delims=" %%A in ("C:\batch files\flag.txt") do set FLAG=%%A
REM バッチファイルと同じフォルダにある flag.txt を絶対パスで指定
set FLAG_FILE=%~dp0flag.txt
for /f "usebackq delims=" %%A in ("%FLAG_FILE%") do set FLAG=%%A
skip=N で先頭 N 行をスキップするか、キー=値形式で読み込んで条件一致したものだけ取得します。
REM 1 行目をスキップして 2 行目を読む
for /f "usebackq skip=1 delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :done
)
:done
REM キー=値形式で STATUS キーのみ取得
for /f "usebackq tokens=1,* delims==" %%K in ("config.txt") do (
if /i "%%K"=="STATUS" set STATUS=%%L
)
タスクスケジューラは作業ディレクトリが C:\Windows\System32 になることがあります。バッチファイルの冒頭で作業ディレクトリを移動するか、絶対パスを使ってください。
@echo off
REM バッチファイルのあるフォルダに移動してから実行
cd /d %~dp0
REM または絶対パスで指定
set FLAG_FILE=%~dp0flag.txt
for /f "usebackq delims=" %%A in ("%FLAG_FILE%") do set FLAG=%%A
goto は setlocal/endlocal ブロックをまたいでもジャンプできます。ただし FOR や IF の括弧ブロック内からは直接ジャンプできません(構文エラーになる)。詳細は ラベル・GOTO・CALL 完全ガイド(ジャンプ・引数渡し) で解説しています。
REM NG: FOR 内の () から直接ラベルへ goto はできない
for /f "delims=" %%A in (flag.txt) do (
set FLAG=%%A
goto :done REM これは FOR の外に出るためここでは可能だが要注意
)
REM OK: ループを 1 回で抜ける用途では使える
for /f "usebackq delims=" %%A in ("flag.txt") do (
set FLAG=%%A
goto :flag_loaded
)
:flag_loaded
はい。CALL で呼び出した場合は終了後に ERRORLEVEL で戻り値を取得できます。単純な成功/失敗の分岐なら ERRORLEVEL の方がシンプルです。詳細は EXIT /B 完全解説(戻り値・ウィンドウを閉じない仕組み) を参照してください。
REM 前処理バッチを CALL で呼び出す
call prepare.bat
REM 戻り値で分岐
if errorlevel 1 (
echo [ERROR] 前処理が失敗しました。
exit /b 1
) else (
echo 前処理成功。メイン処理を開始します。
)
ただし、複数の状態(DONE / RUNNING / ERROR)を伝えたい場合や、並列実行中のバッチ間でステータスを共有したい場合はフラグファイルが適しています。
まとめ
| 目的 | パターン・コマンド |
|---|---|
| 値のみのフラグを読む | for /f "usebackq delims=" %%A in ("flag.txt") do (set FLAG=%%A & goto :done) |
| キー=値を全部読む | for /f "usebackq tokens=1,* delims==" %%K in ("config.txt") do set %%K=%%L |
| フラグで分岐 | if "%FLAG%"=="1" (処理A) else if "%FLAG%"=="0" (処理B) |
| フラグ待機ループ | :loop → 読み込み → 一致チェック → timeout /t N /nobreak → goto :loop |
| タイムアウト管理 | set /a ELAPSED+=N → if !ELAPSED! GEQ !MAX! goto :timeout |
| フラグ書き込み | (echo STATUS=DONE)> status.txt |
| ファイル存在チェック | if not exist "flag.txt" (echo エラー & exit /b 1) |
| 大文字小文字を無視して比較 | if /i "%STATUS%"=="done" |
フラグ待機ループで遅延展開が必要な場合は setlocal enabledelayedexpansion 完全ガイド(FOR内変数・遅延展開) を、バッチ間の連携に CALL を使う場合は ラベル・GOTO・CALL 完全ガイド(ジャンプ・引数渡し) も合わせて確認してください。