【bat】エラー時に処理を中断・終了する方法|EXIT /B・ERRORLEVEL・実務パターンまで完全解説

【bat】エラー時に処理を中断・終了する方法 bat

バッチファイルでは、エラーが発生してもそのまま後続の処理が実行されてしまうことがあります。

Linuxのシェルスクリプトには set -e のようにエラー時に即座にスクリプトを終了させる仕組みがありますが、Windowsバッチファイルにはそのような組み込みの仕組みがありません

そのため、エラーを検知して処理を止めるには、開発者が明示的にエラー判定と中断処理を書く必要があります。

この記事では、バッチファイルでエラー発生時に処理を中断・終了するための方法を、基本から実務レベルまで体系的に解説します。

この記事で学べること

  • ERRORLEVELを使ったエラー判定と中断の基本パターン
  • exit /bexit の違いと使い分け
  • || 演算子による簡潔なエラー処理
  • サブルーチンでのエラー伝播の設計
  • 遅延環境変数展開が必要な場面と対処法
  • パイプ処理でのエラー検知の注意点
  • ログ出力付きエラーハンドリングの実装
  • デプロイ・バックアップスクリプトでの実務的な活用例
  • よくあるミスとトラブルシューティング
スポンサーリンク
  1. ERRORLEVELの基本を理解する
    1. ERRORLEVELとは
    2. ERRORLEVELを参照する2つの方法
    3. ERRORLEVELが変化するタイミング
  2. exit /b で処理を中断する基本形
    1. 基本パターン:コマンド直後に判定
    2. 連続コマンドのエラーチェック
  3. exit /b と exit の違いを理解する
    1. exit /b の使い方
    2. exit(/b なし)を使う場面
    3. goto :eof による終了
  4. || 演算子で簡潔にエラー処理を書く
    1. 基本構文
    2. && 演算子との組み合わせ
    3. || 演算子の注意点
  5. if errorlevel 構文で中断する場合
    1. 基本的な使い方
    2. 複数の終了コードを判定する場合
    3. if NOT errorlevel の活用
  6. サブルーチンでのエラー伝播
    1. 基本パターン:サブルーチンからエラーを返す
    2. 複数のサブルーチンを順番に呼ぶパターン
    3. 外部バッチファイルの呼び出しとエラー伝播
  7. 遅延環境変数展開とエラー判定
    1. 問題のあるコード
    2. 解決策:遅延展開を有効にする
    3. 遅延展開が不要な書き方
  8. パイプ処理でのエラー検知
    1. パイプのERRORLEVEL問題
    2. 解決策:処理を分解する
    3. 一時ファイルを使った中間結果の保存
  9. ログ出力付きエラーハンドリング
    1. 基本的なログ出力パターン
    2. エラーレベル別のログ出力
  10. 実務で使えるエラーハンドリングパターン
    1. デプロイスクリプトのエラーハンドリング
    2. バックアップスクリプトのエラーハンドリング
    3. ビルドスクリプトのエラーハンドリング
  11. エラーコードの設計
    1. エラーコード体系の例
  12. よくあるミスとトラブルシューティング
    1. ミス1: ERRORLEVELの判定が遅い
    2. ミス2: call なしで外部バッチを呼ぶ
    3. ミス3: 括弧内で%ERRORLEVEL%を使う
    4. ミス4: if errorlevel の順序が間違っている
    5. ミス5: exit と exit /b を間違える
  13. エラー処理のパターン一覧
  14. まとめ
  15. バッチファイルのエラーハンドリング一覧

ERRORLEVELの基本を理解する

エラー処理を書く前に、まず ERRORLEVEL の仕組みを正確に理解しておく必要があります。

ERRORLEVELとは

ERRORLEVEL は、直前に実行したコマンドの終了コード(戻り値)を保持する特殊な変数です。

意味 説明
0 正常終了 コマンドが成功した
1 一般的なエラー 多くのコマンドが失敗時に返す
2 ファイル未検出 指定ファイルが見つからない
9009 コマンド未検出 実行しようとしたプログラムが見つからない
負の値 異常終了 プロセスのクラッシュや強制終了

ERRORLEVELを参照する2つの方法

ERRORLEVELの値を参照する方法は2つあり、それぞれ動作が異なります。

構文 動作 用途
if %ERRORLEVEL% neq 0 変数の値を直接比較 正確な値判定に使う
if errorlevel 1 指定値以上かを判定 0/非0の単純判定に使う

注意:if errorlevel 1 は「ERRORLEVELが1以上」を意味します。「1に等しい」ではありません。この仕様を誤解すると、意図しない判定結果になります。

ERRORLEVELが変化するタイミング

ERRORLEVELは外部コマンドの実行後に更新されます。ただし、すべてのコマンドがERRORLEVELを変更するわけではありません。

コマンド ERRORLEVELの変化 備考
copy, xcopy 変更される 成功=0, 失敗=1
del 変更されない場合がある 存在しないファイルの削除でも0のまま
set 変更されない 変数設定は常に成功扱い
echo 変更されない 表示コマンドはERRORLEVELに影響しない
find, findstr 変更される 一致なし=1, 一致あり=0
外部プログラム(.exe等) 変更される プログラムの戻り値がそのまま入る

ポイント:echoset はERRORLEVELを変更しないため、これらをエラー判定の前に挟んでも問題ありません。ただし混乱を避けるため、判定は対象コマンドの直後に行うのが望ましいです。

exit /b で処理を中断する基本形

エラー時に処理を止める最も基本的な方法は、ERRORLEVELを判定して exit /b で処理を終了させるパターンです。

基本パターン:コマンド直後に判定

basic-error-check.bat
REM エラー時に中断する基本形
@echo off

REM コマンドを実行
copy source.txt dest.txt
if %ERRORLEVEL% neq 0 (
  echo エラー: ファイルのコピーに失敗しました
  exit /b 1
)

REM コピー成功後の処理
echo ファイルのコピーが完了しました
exit /b 0

ここで重要なポイントは2つあります。

ポイント:ERRORLEVELの判定は、必ず対象コマンドの直後に行います。間に別のコマンドを挟むと、ERRORLEVELがそのコマンドの結果で上書きされてしまいます。

ポイント:正常終了時にも exit /b 0 を明示的に書くことで、戻り値が不定にならず安全です。

連続コマンドのエラーチェック

複数のコマンドを順番に実行する場合は、各コマンドの直後でERRORLEVELを判定します。

sequential-check.bat
@echo off

REM ステップ1: ディレクトリ作成
mkdir output
if %ERRORLEVEL% neq 0 (
  echo エラー: ディレクトリの作成に失敗しました
  exit /b 1
)

REM ステップ2: ファイルコピー
copy data.csv output\data.csv
if %ERRORLEVEL% neq 0 (
  echo エラー: ファイルのコピーに失敗しました
  exit /b 2
)

REM ステップ3: 処理実行
call process.bat output\data.csv
if %ERRORLEVEL% neq 0 (
  echo エラー: データ処理に失敗しました
  exit /b 3
)

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

各ステップでエラーコードを変えておくと(1, 2, 3)、呼び出し元でどの段階で失敗したかを特定できます。

実行結果(ステップ2で失敗した場合)

エラー: ファイルのコピーに失敗しました

exit /b と exit の違いを理解する

エラー時の終了に使う exit コマンドには、/b オプションの有無で大きな違いがあります。

コマンド 動作 用途 注意点
exit /b [N] 現在のバッチまたはサブルーチンから戻る 通常のエラー中断 コマンドプロンプトは閉じない
exit [N] cmd.exeプロセス自体を終了 致命的エラーでの強制終了 ウィンドウが閉じる
goto :eof バッチ末尾にジャンプして終了 exit /b 0 の代替 戻り値を指定できない

exit /b の使い方

exit /b は「現在のバッチファイルから抜ける」動作です。サブルーチンから呼ばれた場合は、呼び出し元に制御が戻ります。

exit-b-example.bat
@echo off
echo 処理を開始します

somecommand
if %ERRORLEVEL% neq 0 (
  echo エラーが発生しました。中断します。
  exit /b 1
)

echo 処理が正常に完了しました
exit /b 0

この場合、エラーが発生してもコマンドプロンプトは開いたままで、ユーザーはエラーメッセージを確認できます。

exit(/b なし)を使う場面

exit(/b なし)は cmd.exe プロセス自体を終了します。主にタスクスケジューラから実行されるバッチファイルで使います。

scheduled-task.bat
@echo off
REM タスクスケジューラから実行されるバッチ

call backup.bat
if %ERRORLEVEL% neq 0 (
  echo バックアップ失敗 >> error.log
  exit 1
)
exit 0

注意:対話的なコマンドプロンプトで exit(/b なし)を実行すると、ウィンドウが閉じてしまいます。手動実行するバッチでは必ず exit /b を使ってください。

goto :eof による終了

goto :eof はバッチファイルの末尾に移動して終了する書き方です。exit /b 0 と同様の効果がありますが、戻り値を指定できません。

goto-eof-example.bat
@echo off

if not exist config.ini (
  echo 設定ファイルが見つかりません
  goto :eof
)

REM 設定ファイルが存在する場合の処理
echo 設定を読み込みます

ポイント:エラー時に戻り値を返す必要がある場合は exit /b N を使い、単に処理を中断するだけなら goto :eof も使えます。実務では戻り値を明示する exit /b を推奨します。

|| 演算子で簡潔にエラー処理を書く

バッチファイルでは ||(条件付き実行演算子)を使うと、エラー時の処理を1行で書けます。

基本構文

|| は「左辺のコマンドが失敗した場合に右辺を実行する」という動作です。

|| 演算子の基本
REM 基本構文: コマンド || エラー時の処理
copy source.txt dest.txt || exit /b 1

REM メッセージ付き
copy source.txt dest.txt || (echo コピー失敗 & exit /b 1)

&& 演算子との組み合わせ

&& は「左辺が成功した場合に右辺を実行する」演算子です。|| と組み合わせることで、成功時・失敗時の両方の処理を記述できます。

演算子 動作
&& 左辺が成功したら右辺を実行 copy a b && echo 成功
|| 左辺が失敗したら右辺を実行 copy a b || echo 失敗
& 結果に関係なく右辺を実行 copy a b & echo 完了
演算子の組み合わせ
@echo off

REM 成功なら次へ、失敗なら中断
copy source.txt dest.txt && echo コピー成功 || (echo コピー失敗 & exit /b 1)

REM 連続実行(すべて成功する必要がある)
mkdir output && copy data.csv output\ && echo 準備完了 || (echo 準備失敗 & exit /b 1)

|| 演算子の注意点

|| は便利ですが、すべてのコマンドで期待通りに動くわけではありません。

注意:|| は ERRORLEVELではなく、コマンドの「成功/失敗」を判定します。一部の内部コマンド(set など)は失敗しても || の右辺が実行されないことがあります。確実にエラーを捕捉したい場合は if %ERRORLEVEL% neq 0 を使ってください。

if errorlevel 構文で中断する場合

if errorlevel N 構文は「ERRORLEVELがN以上」かどうかを判定します。この「以上」という仕様を正しく理解して使う必要があります。

基本的な使い方

if errorlevel の基本
REM ERRORLEVEL が 1 以上(= エラー)なら中断
somecommand
if errorlevel 1 (
  echo エラーが発生しました
  exit /b 1
)

複数の終了コードを判定する場合

「以上」判定であるため、複数の終了コードを分岐する場合は大きい値から順に判定する必要があります。

複数コードの判定(正しい順序)
somecommand

REM 大きい値から順に判定する(重要)
if errorlevel 3 (
  echo 致命的エラー(コード3以上)
  exit /b 3
)
if errorlevel 2 (
  echo 警告エラー(コード2)
  exit /b 2
)
if errorlevel 1 (
  echo 軽微なエラー(コード1)
  exit /b 1
)

echo 正常終了(コード0)

注意:小さい値から判定すると、例えばERRORLEVELが3でも if errorlevel 1 がtrue になってしまい、正確な分岐ができません。

if NOT errorlevel の活用

if NOT errorlevel 1 は「ERRORLEVELが1未満(つまり0)」を意味します。正常時のみ処理を続行したい場合に便利です。

if NOT errorlevel の活用
@echo off

copy source.txt dest.txt

REM 正常時のみ次の処理を実行
if NOT errorlevel 1 (
  echo コピー成功。次の処理へ進みます
  call next-step.bat
) else (
  echo コピー失敗。処理を中断します
  exit /b 1
)

サブルーチンでのエラー伝播

call を使って処理をサブルーチンに分割している場合、エラーの伝播を正しく設計する必要があります。サブルーチン内で exit /b を使って終了コードを返し、呼び出し元でそれを判定するのが基本パターンです。

基本パターン:サブルーチンからエラーを返す

subroutine-error.bat
@echo off

call :validate_input
if %ERRORLEVEL% neq 0 (
  echo 入力検証でエラーが発生しました
  exit /b 1
)

call :process_data
if %ERRORLEVEL% neq 0 (
  echo データ処理でエラーが発生しました
  exit /b 2
)

echo 全処理が正常に完了しました
exit /b 0

:validate_input
if not exist input.csv (
  echo 入力ファイルが存在しません
  exit /b 1
)
exit /b 0

:process_data
copy input.csv output.csv
if %ERRORLEVEL% neq 0 exit /b 1
exit /b 0

ポイント:サブルーチン内で exit /b を使うと、サブルーチンからのみ抜けて呼び出し元に戻ります。呼び出し元で ERRORLEVEL を判定し、必要に応じてスクリプト全体を終了します。

複数のサブルーチンを順番に呼ぶパターン

実務では、複数のサブルーチンを順番に実行し、どれか1つでも失敗したら全体を中断する構成がよく使われます。

multi-subroutine.bat
@echo off

REM 各ステップを順番に実行
call :step1 || goto :error
call :step2 || goto :error
call :step3 || goto :error

echo 全ステップ完了
exit /b 0

:error
echo エラーが発生しました(コード: %ERRORLEVEL%)
exit /b 1

:step1
echo ステップ1を実行中...
mkdir output 2>nul
exit /b 0

:step2
echo ステップ2を実行中...
copy data.csv output\
exit /b %ERRORLEVEL%

:step3
echo ステップ3を実行中...
call process.bat output\data.csv
exit /b %ERRORLEVEL%

この構成では call :stepN || goto :error の1行で「失敗したらエラー処理へ飛ぶ」を実現しています。エラー処理を1箇所にまとめられるため、コードの見通しが良くなります。

外部バッチファイルの呼び出しとエラー伝播

call で外部のバッチファイルを呼ぶ場合も、呼び出し先が exit /b で終了コードを返せば、呼び出し元で判定できます。

call-external.bat
@echo off

REM 外部バッチを呼び出し
call C:\scripts\validate.bat %1
if %ERRORLEVEL% neq 0 (
  echo validate.bat がエラーを返しました
  exit /b %ERRORLEVEL%
)

REM 注意: call なしで実行すると制御が戻ってこない
REM C:\scripts\validate.bat %1   ← これだと後続処理が実行されない

注意:外部バッチファイルを call なしで実行すると、呼び出し先のバッチが終了した時点でスクリプト全体が終了します。後続処理やエラー判定を行いたい場合は必ず call を付けてください。

遅延環境変数展開とエラー判定

括弧ブロック(iffor の中)では、%ERRORLEVEL% がブロック全体の解析時に一度だけ展開されるため、ブロック内でのERRORLEVELの変化が反映されません。これを解決するのが遅延環境変数展開です。

問題のあるコード

問題のあるコード(%で展開)
@echo off

REM このコードは正しく動かない場合がある
for %%f in (file1.txt file2.txt file3.txt) do (
  copy %%f backup\
  if %ERRORLEVEL% neq 0 (
    REM ← この判定はブロック先頭の値を見ている
    echo %%f のコピーに失敗
    exit /b 1
  )
)

上記のコードでは、%ERRORLEVEL%for ブロック全体が解析された時点の値(ブロック実行前の値)で固定されます。つまり、ループ内でコマンドが失敗しても、判定が正しく行われません。

解決策:遅延展開を有効にする

setlocal enabledelayedexpansion を宣言し、!ERRORLEVEL!(感嘆符)で参照すると、実行時の最新の値が取得できます。

遅延展開で正しく判定
@echo off
setlocal enabledelayedexpansion

for %%f in (file1.txt file2.txt file3.txt) do (
  copy %%f backup\
  if !ERRORLEVEL! neq 0 (
    echo %%f のコピーに失敗しました
    exit /b 1
  )
)

echo すべてのファイルのコピーが完了しました
endlocal
exit /b 0
構文 展開タイミング 括弧ブロック内
%ERRORLEVEL% ブロック解析時(1回だけ) 値が固定される
!ERRORLEVEL! コマンド実行時(毎回) 最新の値を取得

遅延展開が不要な書き方

|| 演算子を使えば、遅延展開なしでもループ内のエラーを検知できます。

|| で遅延展開を回避
@echo off

for %%f in (file1.txt file2.txt file3.txt) do (
  copy %%f backup\ || (
    echo %%f のコピーに失敗
    exit /b 1
  )
)

パイプ処理でのエラー検知

パイプ(|)を使った処理では、ERRORLEVELはパイプの最後のコマンドの結果のみを反映します。前段のコマンドが失敗しても、後段が成功すれば ERRORLEVEL は 0 になります。

パイプのERRORLEVEL問題

パイプの問題例
REM type が失敗しても find が成功すれば ERRORLEVEL は 0
type nonexistent.txt | find "keyword"
echo ERRORLEVEL: %ERRORLEVEL%
REM → ERRORLEVEL: 1(findが一致なしのため)
REM   ※ type の失敗は検知できない

解決策:処理を分解する

パイプの各段階でエラーを検知したい場合は、処理を分解して個別に判定します。

パイプを分解して判定
@echo off

REM ステップ1: ファイル存在確認
if not exist data.txt (
  echo ファイルが見つかりません
  exit /b 1
)

REM ステップ2: ファイル読み取りテスト
type data.txt > nul 2>&1
if %ERRORLEVEL% neq 0 (
  echo ファイルの読み取りに失敗
  exit /b 2
)

REM ステップ3: 検索実行
findstr "keyword" data.txt > nul
if %ERRORLEVEL% neq 0 (
  echo キーワードが見つかりません
  exit /b 3
)

echo キーワードが見つかりました

この方法なら、どの段階で問題が発生したかを正確に特定できます。

一時ファイルを使った中間結果の保存

パイプの代わりに一時ファイルに出力し、次のコマンドでそれを読み取る方法もあります。

一時ファイルで中間結果を保存
@echo off
set TMPFILE=%TEMP%\pipe_tmp_%RANDOM%.txt

REM 前段の出力を一時ファイルに保存
type data.txt > %TMPFILE%
if %ERRORLEVEL% neq 0 (
  echo 前段処理失敗
  del %TMPFILE% 2>nul
  exit /b 1
)

REM 後段の処理
findstr "keyword" %TMPFILE%
set RESULT=%ERRORLEVEL%
del %TMPFILE% 2>nul

if %RESULT% neq 0 (
  echo 後段処理失敗
  exit /b 2
)

ログ出力付きエラーハンドリング

実務で運用するバッチファイルでは、エラーが発生した事実だけでなく、いつ・何が・なぜ失敗したかをログに残すことが重要です。

基本的なログ出力パターン

logging-basic.bat
@echo off
set LOGFILE=C:\logs\batch_%date:~0,4%%date:~5,2%%date:~8,2%.log

REM ログ出力サブルーチン
call :log "処理を開始します"

copy source.txt dest.txt
if %ERRORLEVEL% neq 0 (
  call :log "ERROR: ファイルコピー失敗(code: %ERRORLEVEL%)"
  exit /b 1
)
call :log "ファイルコピー完了"

call :log "処理が正常に完了しました"
exit /b 0

:log
echo [%date% %time%] %~1
echo [%date% %time%] %~1 >> %LOGFILE%
exit /b 0

ログ出力例

[2026/03/06 14:30:15.42] 処理を開始します
[2026/03/06 14:30:15.48] ファイルコピー完了
[2026/03/06 14:30:15.50] 処理が正常に完了しました

エラーレベル別のログ出力

エラーの深刻度に応じてログレベルを分けると、運用時の問題特定が容易になります。

logging-levels.bat
@echo off
set LOGFILE=C:\logs\app.log

REM INFO レベル
call :log_info "バックアップを開始します"

xcopy C:\data C:\backup\ /E /Y
if %ERRORLEVEL% neq 0 (
  call :log_error "バックアップ失敗(code: %ERRORLEVEL%)"
  exit /b 1
)
call :log_info "バックアップ完了"
exit /b 0

:log_info
echo [%date% %time%] [INFO] %~1 >> %LOGFILE%
exit /b 0

:log_warn
echo [%date% %time%] [WARN] %~1 >> %LOGFILE%
exit /b 0

:log_error
echo [%date% %time%] [ERROR] %~1 >> %LOGFILE%
echo [%date% %time%] [ERROR] %~1 >&2
exit /b 0

ポイント:ERROR レベルのログは標準エラー出力(>&2)にも出力しておくと、タスクスケジューラやCI/CDツールでのエラー検知に役立ちます。

実務で使えるエラーハンドリングパターン

ここからは、実務でよく使われるバッチファイルのエラーハンドリングパターンを紹介します。

デプロイスクリプトのエラーハンドリング

ファイルをサーバーにデプロイする際、各ステップでエラーチェックを行い、問題があれば即座にロールバックする構成です。

deploy.bat
@echo off
setlocal
set DEPLOY_DIR=C:\inetpub\wwwroot\myapp
set BACKUP_DIR=C:\backup\myapp_%date:~0,4%%date:~5,2%%date:~8,2%
set SOURCE_DIR=C:\release\myapp
set LOGFILE=C:\logs\deploy.log

call :log "=== デプロイ開始 ==="

REM ステップ1: ソースの存在確認
if not exist %SOURCE_DIR%\ (
  call :log "ERROR: ソースディレクトリが見つかりません"
  exit /b 1
)

REM ステップ2: 現在のファイルをバックアップ
call :log "バックアップ作成中..."
xcopy %DEPLOY_DIR% %BACKUP_DIR%\ /E /I /Y > nul
if %ERRORLEVEL% neq 0 (
  call :log "ERROR: バックアップの作成に失敗"
  exit /b 2
)

REM ステップ3: 新しいファイルをデプロイ
call :log "デプロイ中..."
xcopy %SOURCE_DIR% %DEPLOY_DIR%\ /E /Y > nul
if %ERRORLEVEL% neq 0 (
  call :log "ERROR: デプロイ失敗。ロールバックします"
  call :rollback
  exit /b 3
)

call :log "=== デプロイ完了 ==="
exit /b 0

:rollback
call :log "ロールバック実行中..."
xcopy %BACKUP_DIR% %DEPLOY_DIR%\ /E /Y > nul
if %ERRORLEVEL% neq 0 (
  call :log "FATAL: ロールバックにも失敗しました"
)
exit /b 0

:log
echo [%date% %time%] %~1
echo [%date% %time%] %~1 >> %LOGFILE%
exit /b 0

正常時の実行結果

[2026/03/06 10:00:00.00] === デプロイ開始 ===
[2026/03/06 10:00:01.23] バックアップ作成中...
[2026/03/06 10:00:05.67] デプロイ中...
[2026/03/06 10:00:08.90] === デプロイ完了 ===

バックアップスクリプトのエラーハンドリング

日次バックアップのバッチファイルでは、ディスク容量の確認、バックアップの実行、古いバックアップの削除など、各段階でエラーチェックが必要です。

backup.bat
@echo off
setlocal enabledelayedexpansion
set SRC=C:\important-data
set DST=D:\backup\%date:~0,4%%date:~5,2%%date:~8,2%
set LOGFILE=D:\backup\backup.log
set KEEP_DAYS=30
set EXIT_CODE=0

call :log "=== バックアップ開始 ==="

REM ソースディレクトリの確認
if not exist %SRC%\ (
  call :log "ERROR: ソースが見つかりません: %SRC%"
  exit /b 1
)

REM バックアップ先ディレクトリの作成
mkdir %DST% 2>nul
if not exist %DST%\ (
  call :log "ERROR: バックアップ先を作成できません"
  exit /b 2
)

REM バックアップ実行
xcopy %SRC% %DST%\ /E /I /Y /Q
if !ERRORLEVEL! neq 0 (
  call :log "ERROR: バックアップ失敗"
  set EXIT_CODE=3
  goto :cleanup
)
call :log "バックアップ完了: %DST%"

REM 古いバックアップの削除
call :log "%KEEP_DAYS%日以上前のバックアップを削除中..."
forfiles /P D:\backup /D -%KEEP_DAYS% /C "cmd /c if @isdir==TRUE rd /s /q @path" 2>nul

:cleanup
call :log "=== バックアップ終了(code: %EXIT_CODE%)==="
endlocal & exit /b %EXIT_CODE%

:log
echo [%date% %time%] %~1
echo [%date% %time%] %~1 >> %LOGFILE%
exit /b 0

ビルドスクリプトのエラーハンドリング

コンパイルやテストを含むビルドプロセスでは、各段階の失敗を検知して適切に中断することが重要です。

build.bat
@echo off
setlocal

echo === ビルド開始 ===

REM 1. 依存関係のインストール
echo [1/4] 依存関係をインストール中...
npm install
if %ERRORLEVEL% neq 0 (
  echo ERROR: npm install 失敗
  exit /b 1
)

REM 2. Lint チェック
echo [2/4] Lintチェック中...
npm run lint
if %ERRORLEVEL% neq 0 (
  echo ERROR: Lint エラーがあります
  exit /b 2
)

REM 3. テスト実行
echo [3/4] テスト実行中...
npm test
if %ERRORLEVEL% neq 0 (
  echo ERROR: テスト失敗
  exit /b 3
)

REM 4. ビルド
echo [4/4] ビルド中...
npm run build
if %ERRORLEVEL% neq 0 (
  echo ERROR: ビルド失敗
  exit /b 4
)

echo === ビルド成功 ===
exit /b 0

エラーコードの設計

バッチファイルで exit /b N の引数として返すエラーコードは、あらかじめ設計しておくと運用が楽になります。

エラーコード体系の例

コード 意味
0 正常終了 すべての処理が成功
1 引数エラー 必要な引数が不足、不正な値
2 ファイルエラー 入力ファイルが存在しない
3 処理エラー コマンド実行が失敗
4 ネットワークエラー 通信先に接続できない
99 想定外のエラー 予期しない例外
エラーコードを活用した呼び出し元
@echo off

call process.bat input.csv
set RC=%ERRORLEVEL%

if %RC% equ 0 echo 正常終了
if %RC% equ 1 echo 引数エラー: 入力を確認してください
if %RC% equ 2 echo ファイルエラー: ファイルが見つかりません
if %RC% equ 3 echo 処理エラー: 実行中に問題が発生
if %RC% gtr 3 echo 想定外のエラー(code: %RC%)

exit /b %RC%

ポイント:ERRORLEVELの値を変数(%RC%)に退避しておくと、後続のコマンド実行でERRORLEVELが上書きされても元の値を参照できます。

よくあるミスとトラブルシューティング

バッチファイルのエラーハンドリングでよく見かけるミスと、その対処法をまとめます。

ミス1: ERRORLEVELの判定が遅い

NG: 判定前に別コマンドを挟む
REM NG: echoは問題ないが、他のコマンドだとERRORLEVELが変わる
copy source.txt dest.txt
dir output\   ← ここでERRORLEVELが上書きされる
if %ERRORLEVEL% neq 0 (
  echo copyのエラー? いいえ、dirの結果です
)
OK: 判定を直後に行う
copy source.txt dest.txt
if %ERRORLEVEL% neq 0 (
  echo コピーに失敗しました
  exit /b 1
)
dir output\

ミス2: call なしで外部バッチを呼ぶ

NG: call なし
REM NG: sub.bat の終了後、ここに戻ってこない
sub.bat
echo この行は実行されない
OK: call を付ける
REM OK: call で呼ぶと制御が戻る
call sub.bat
if %ERRORLEVEL% neq 0 exit /b 1
echo sub.bat が成功しました

ミス3: 括弧内で%ERRORLEVEL%を使う

NG: 括弧内で%を使う
REM NG: ブロック内で値が固定される
for %%f in (a.txt b.txt) do (
  copy %%f backup\
  if %ERRORLEVEL% neq 0 echo 失敗
)
OK: 遅延展開を使う
setlocal enabledelayedexpansion
for %%f in (a.txt b.txt) do (
  copy %%f backup\
  if !ERRORLEVEL! neq 0 echo %%f のコピー失敗
)

ミス4: if errorlevel の順序が間違っている

NG: 小さい値から判定
REM NG: ERRORLEVEL=3 でも「エラー1」になる
if errorlevel 1 echo エラー1
if errorlevel 2 echo エラー2
if errorlevel 3 echo エラー3
OK: 大きい値から判定
REM OK: 大きい値から判定する
if errorlevel 3 (
  echo エラー3
  exit /b 3
)
if errorlevel 2 (
  echo エラー2
  exit /b 2
)
if errorlevel 1 (
  echo エラー1
  exit /b 1
)

ミス5: exit と exit /b を間違える

対話型コマンドプロンプトで exit(/b なし)を使うと、ウィンドウが閉じます。デバッグ中にエラーメッセージが見えなくなるのはよくある問題です。

ポイント:迷ったら exit /b を使ってください。exit(/b なし)が必要な場面は、タスクスケジューラやCI環境からの実行など、限られたケースだけです。

エラー処理のパターン一覧

ここまで紹介したエラー処理パターンを一覧表にまとめます。

パターン 構文 特徴 推奨場面
基本判定 if %ERRORLEVEL% neq 0 正確で汎用的 全般(最も推奨)
以上判定 if errorlevel 1 短く書ける 0/非0の単純分岐
||演算子 command || exit /b 1 1行で書ける 簡潔な記述が必要な場面
遅延展開 if !ERRORLEVEL! neq 0 ブロック内で正確 for/ifブロック内
変数退避 set RC=%ERRORLEVEL% 後から参照可能 複数箇所で参照する場合
gotoパターン call :sub || goto :error エラー処理を集約 複数ステップの処理

まとめ

バッチファイルでエラー時に処理を中断・終了するには、エラーを検知した瞬間に exit /b で制御を切る設計が基本になります。

ここまでの内容を整理すると、次のようになります。

この記事のまとめ

  • ERRORLEVEL は直前のコマンドの終了コードを保持する。判定は直後に行う
  • exit /b N で現在のバッチから抜ける。exit N はcmd.exeごと終了する
  • || 演算子で「失敗したら中断」を1行で書ける
  • サブルーチンでは exit /b で終了コードを返し、呼び出し元で判定する
  • 括弧ブロック内では遅延展開(!ERRORLEVEL!)を使うか、|| で代替する
  • パイプでは最後のコマンドのERRORLEVELしか取得できない。処理を分解して個別に判定する
  • 実務ではログ出力・エラーコード設計・ロールバック機構を組み合わせる

ERRORLEVELは直前のコマンドの結果であることを常に意識し、判定を後回しにしない構成が重要です。

中断条件と戻り値を明示的に設計することで、バッチファイルは「止まらない不安定な処理」から「信頼できる制御フロー」へと変わります。

バッチファイルのエラーハンドリング一覧

目的や状況別に、エラー処理の方法を整理しています。