バッチファイル(.bat / .cmd)でコマンドを実行した際に「エラーが発生したか」を判定することは、安定したスクリプトを書くうえで避けて通れないテーマです。
しかし、単に if %ERRORLEVEL% neq 0 と書けばよいという単純な話ではありません。ERRORLEVELには2種類の参照方法があり、それぞれ挙動が異なります。さらに、コマンドごとに終了コードの意味が違い、遅延展開の有無で結果が変わり、パイプやリダイレクトとの組み合わせで想定外の値になることもあります。
本記事では、バッチファイルのエラー判定について基本から実務レベルまで徹底的に解説します。ERRORLEVELの2つの参照方法の違い、|| / && 演算子による条件実行、主要コマンドの終了コード一覧、遅延展開との関係、実務的なエラー判定パターン(ファイルコピー・DB接続・サービス確認等)、そしてエラーログの出力方法まで、コピペで使えるサンプルコード付きで網羅的にカバーします。
この記事で学べること
%ERRORLEVEL% と if errorlevel N の違いと使い分け
||(失敗時実行)と &&(成功時実行)による条件分岐
- 主要コマンド(robocopy / find / xcopy 等)の終了コード一覧
- 遅延展開(
!ERRORLEVEL!)が必要になる場面と書き方
- 実務で使えるエラー判定テンプレート(ファイル操作・DB接続・サービス確認)
- エラーログの出力とローテーション
| セクション |
内容 |
レベル |
| ERRORLEVELの基本 |
仕組み・2つの参照方法・注意点 |
入門 |
| 条件演算子 |
|| と && による条件実行 |
基本 |
| 終了コード一覧 |
主要コマンドの終了コードと意味 |
基本 |
| 遅延展開 |
括弧ブロック・FORループでの判定 |
中級 |
| 実務パターン集 |
ファイル操作・DB接続・サービス確認等 |
中級〜上級 |
| エラーログ |
ログ出力・ローテーション・通知 |
上級 |
| トラブルシューティング |
よくある問題と解決策 |
全レベル |
ERRORLEVELとは何か ── エラー判定の基盤を理解する
ERRORLEVELは、バッチファイルにおける直前に実行されたコマンドの終了コード(戻り値)を保持する特殊な仕組みです。多くのプログラミング言語でいう「関数の戻り値」に相当します。
ただし、ERRORLEVELには一般的な変数と異なる特殊な性質があります。以下の表で基本仕様を整理します。
| 特性 |
説明 |
| 値の範囲 |
0 〜 255(一部コマンドは負の値や255超を返すこともあるが、通常は0〜255) |
| 初期値 |
バッチファイルの実行開始時は 0 |
| 上書きタイミング |
各コマンドの実行完了後に、そのコマンドの終了コードで上書きされる |
| 蓄積性 |
蓄積されない。直前のコマンドの結果のみ保持 |
| 0 の意味 |
ほとんどのコマンドで「正常終了」を意味する(例外あり) |
| 0以外の意味 |
コマンドごとに意味が異なる。必ずしも「致命的エラー」ではない |
注意:ERRORLEVELは「エラーレベル」という名前ですが、実際には終了コード(exit code)と呼ぶ方が正確です。0以外の値が必ずしもエラーを意味するわけではないため、「ERRORLEVELが0以外=エラー」という固定観念は危険です。
ERRORLEVELの値がセットされる流れ
ERRORLEVELがどのタイミングで、どのような値にセットされるのかを具体的に見てみましょう。
ERRORLEVELの変化を追跡する
@echo off
rem この時点では ERRORLEVEL = 0(初期値)
echo 開始時: %ERRORLEVEL%
rem 存在するファイルをコピー → 成功 → ERRORLEVEL = 0
copy C:\Windows\System32\notepad.exe C:\temp\test.exe >nul 2>&1
echo copy成功後: %ERRORLEVEL%
rem 存在しないファイルをコピー → 失敗 → ERRORLEVEL = 1
copy C:\notexist\dummy.txt C:\temp\ >nul 2>&1
echo copy失敗後: %ERRORLEVEL%
rem echoは常に成功 → ERRORLEVEL = 0 に戻る
echo echoを挟んだ後: %ERRORLEVEL%
実行結果
開始時: 0
copy成功後: 0
copy失敗後: 1
echoを挟んだ後: 0
注目すべきは最後の行です。echoコマンドは常に成功するため、ERRORLEVELが0にリセットされます。これが「判定前にechoを挟むとエラーを見逃す」という典型的なバグの原因です。
ポイント:ERRORLEVELは「蓄積」されません。直前のコマンドの結果で毎回上書きされるため、エラー判定は対象コマンドの直後で行うのが鉄則です。間にechoやsetなど別のコマンドを挟むと、そのコマンドの終了コードで上書きされてしまいます。
2つの参照方法 ── %ERRORLEVEL% と if errorlevel N
ERRORLEVELを参照する方法は大きく分けて2つあります。それぞれ構文も判定ロジックも異なるため、違いを正確に理解しておく必要があります。
方法1: %ERRORLEVEL%(環境変数として参照)
%ERRORLEVEL% は、終了コードの具体的な数値を環境変数として取得する書き方です。if 文と組み合わせて等号・不等号で比較できます。
%ERRORLEVEL% による判定
@echo off
somecommand
rem 等号比較(0と等しい場合)
if %ERRORLEVEL% equ 0 (
echo 正常終了
)
rem 不等号比較(0以外の場合)
if %ERRORLEVEL% neq 0 (
echo エラーが発生しました(終了コード: %ERRORLEVEL%)
exit /b 1
)
rem 特定の値との比較
if %ERRORLEVEL% equ 2 (
echo 終了コード2が返されました
)
%ERRORLEVEL% では equ(等しい)、neq(等しくない)、lss(より小さい)、leq(以下)、gtr(より大きい)、geq(以上)の比較演算子が使えます。
方法2: if errorlevel N(閾値判定)
if errorlevel N は、終了コードがN 以上かどうかを判定する構文です。等号比較ではないことが最大の注意点です。
if errorlevel N による判定
@echo off
somecommand
rem ERRORLEVEL が 1 以上のとき TRUE
if errorlevel 1 (
echo エラーが発生しました
exit /b 1
)
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
)
注意:if errorlevel 1 は「ERRORLEVELが1と等しい」ではなく「ERRORLEVELが1以上」という意味です。複数のコードを判定するときは必ず大きい数値から順にチェックする必要があります。
2つの方法の比較表
| 比較項目 |
%ERRORLEVEL% |
if errorlevel N |
| 判定方式 |
等号・不等号による厳密比較 |
N以上かどうかの閾値判定 |
| 使える比較 |
equ, neq, lss, leq, gtr, geq |
N以上のみ(not errorlevel で否定可) |
| 括弧ブロック内 |
遅延展開なしだと値が固定される |
常に最新値を参照できる |
| 環境変数の影響 |
set ERRORLEVEL=値 で上書きされると壊れる |
set ERRORLEVEL=値 の影響を受けない |
| 可読性 |
直感的で読みやすい |
「以上」の仕様を知らないと誤読しやすい |
| 推奨場面 |
特定の終了コードを厳密に判定 |
0/非0の単純判定、古い環境との互換性 |
ポイント:実務では if %ERRORLEVEL% neq 0 が最もよく使われます。等号比較が直感的で、特定の終了コードも判定できるためです。ただし、括弧ブロック内で使う場合は遅延展開が必要になる点に注意してください。
if errorlevel を使った複数コードの判定テンプレート
if errorlevel で複数の終了コードを分岐させる場合、大きい値から順に判定する必要があります。以下は xcopy の終了コードを判定する例です。
xcopyの終了コードを判定する(大きい値から順)
@echo off
xcopy C:\source C:\dest /E /I /Y >nul 2>&1
rem 大きい値から順に判定(重要!)
if errorlevel 5 (
echo [FATAL] ディスク書き込みエラー
exit /b 5
)
if errorlevel 4 (
echo [ERROR] メモリ不足またはディスク容量不足
exit /b 4
)
if errorlevel 2 (
echo [WARN] Ctrl+C で中断されました
exit /b 2
)
if errorlevel 1 (
echo [ERROR] コピー対象のファイルが見つかりません
exit /b 1
)
echo [OK] コピーが正常に完了しました
exit /b 0
|| と && 演算子 ── ERRORLEVELを使わない条件実行
ERRORLEVELを明示的に参照しなくても、&&(成功時実行)と ||(失敗時実行)を使えば、直前のコマンドの成否に応じて後続処理を分岐できます。Linux/macOSのシェルスクリプトと同じ考え方です。
| 演算子 |
動作 |
用途 |
&& |
前のコマンドが成功(ERRORLEVEL=0)した場合のみ後続を実行 |
成功時の続行処理 |
|| |
前のコマンドが失敗(ERRORLEVEL≠0)した場合のみ後続を実行 |
エラー時のフォールバック |
& |
前のコマンドの成否に関わらず後続を実行 |
無条件の連続実行 |
&& 演算子の使い方(成功時のみ実行)
コマンドを && で連結すると、途中で1つでも失敗すればそこで実行が止まります。連続処理の安全な実行に適しています。
&& で安全に連続実行
@echo off
rem 3つのコマンドを順に実行。途中で失敗したら停止
mkdir C:\work\output 2>nul && (
echo [1/3] ディレクトリ作成: OK
) && (
copy C:\work\data.csv C:\work\output\ >nul
echo [2/3] ファイルコピー: OK
) && (
echo [3/3] すべて完了
)
rem 1行で書く場合
mkdir output && copy data.csv output\ && echo 完了
|| 演算子の使い方(失敗時のみ実行)
|| はエラー発生時のフォールバック処理に使います。最もシンプルなエラーハンドリングの方法です。
|| でエラー時に処理を分岐
@echo off
rem 失敗時にエラーメッセージを表示して終了
copy source.txt dest.txt >nul 2>&1 || (
echo [ERROR] ファイルコピーに失敗しました
exit /b 1
)
rem 1行で書く場合
copy source.txt dest.txt >nul || exit /b 1
rem ping失敗時にエラーログに記録
ping -n 1 example.com >nul 2>&1 || (
echo %DATE% %TIME% ネットワーク接続に失敗 >> error.log
exit /b 1
)
&& と || を組み合わせた実用パターン
2つの演算子を組み合わせることで、成功・失敗の両方のケースを1行で処理できます。
&& と || の組み合わせ
@echo off
rem 成功なら「OK」、失敗なら「NG」
ping -n 1 google.com >nul 2>&1 && (
echo [OK] ネットワーク接続正常
) || (
echo [NG] ネットワーク接続失敗
)
rem ファイルの存在確認 → コピー → 完了報告
if exist "source.txt" (
copy source.txt backup\ >nul && echo バックアップ完了 || echo コピー失敗
) else (
echo コピー元ファイルが存在しません
)
ポイント:|| と && は、if %ERRORLEVEL% を使うよりも簡潔にエラー処理を書けます。ただし、複雑な分岐や特定の終了コード値による判定が必要な場合は %ERRORLEVEL% を使う方が適切です。用途に応じて使い分けましょう。
主要コマンドの終了コード一覧
バッチファイルでエラー判定を正しく行うには、各コマンドの終了コードの意味を把握しておくことが不可欠です。「0以外=エラー」とは限らないコマンドが多数あります。
基本コマンド
| コマンド |
終了コード |
意味 |
| copy |
0 |
コピー成功 |
| 1 |
コピー失敗(ファイルが見つからない等) |
| del |
0 |
削除成功(対象ファイルが存在しない場合も0) |
| 1 |
削除失敗(アクセス拒否等) |
| mkdir / md |
0 |
作成成功 |
| 1 |
作成失敗(既に存在する等) |
| move |
0 |
移動成功 |
| 1 |
移動失敗 |
| rename / ren |
0 |
リネーム成功 |
| 1 |
リネーム失敗 |
検索コマンド
| コマンド |
終了コード |
意味 |
| find |
0 |
検索文字列が見つかった |
| 1 |
検索文字列が見つからなかった(エラーではない) |
| 2 |
ファイルが開けない等の真のエラー |
| findstr |
0 |
一致する行が見つかった |
| 1 |
一致する行が見つからなかった(エラーではない) |
| 2 |
構文エラーまたはファイルアクセスエラー |
注意:find / findstr の ERRORLEVEL=1 は「見つからなかった」であり、エラーではありません。if %ERRORLEVEL% neq 0 で一律エラー扱いすると、正常な「不一致」をエラーとして誤検知してしまいます。
ファイルコピー系コマンド(特殊な終了コード)
| コマンド |
終了コード |
意味 |
| xcopy |
0 |
正常にコピー完了 |
| 1 |
コピーするファイルが見つからない |
| 2 |
Ctrl+C で中断 |
| 4 |
初期化エラー(メモリ不足/構文エラー等) |
| 5 |
ディスク書き込みエラー |
| – |
(3は未使用) |
| robocopy |
0 |
コピーなし(変更なし) |
| 1 |
正常にコピー完了 |
| 2〜7 |
追加情報あり(余分なファイル検出等)だが正常 |
| 8〜15 |
一部または全部のコピーに失敗 |
| 16 |
致命的エラー(コピーゼロ) |
注意:robocopyは0〜7が正常、8以上がエラーです。if %ERRORLEVEL% neq 0 で判定すると、正常なコピー(ERRORLEVEL=1)をエラーとして扱ってしまう重大なバグになります。robocopyのエラー判定は if %ERRORLEVEL% geq 8 とする必要があります。
robocopyの正しいエラー判定
robocopyのエラー判定(0〜7が正常)
@echo off
robocopy C:\source C:\dest /MIR /R:3 /W:5
set RC=%ERRORLEVEL%
rem robocopyは 0〜7 が正常、8以上がエラー
if %RC% geq 8 (
echo [ERROR] robocopy失敗(終了コード: %RC%)
exit /b 1
)
rem 正常終了(0〜7の詳細を出し分けたい場合)
if %RC% equ 0 echo [INFO] 変更なし
if %RC% equ 1 echo [OK] コピー完了
if %RC% geq 2 if %RC% leq 7 echo [OK] コピー完了(追加情報あり: %RC%)
exit /b 0
ネットワーク・その他コマンド
| コマンド |
終了コード |
意味 |
| ping |
0 |
応答あり |
| 1 |
応答なし(タイムアウト/ホスト不明) |
| net use |
0 |
接続/切断成功 |
| 2 |
接続失敗(ネットワークエラー等) |
| sc query |
0 |
サービス情報取得成功 |
| 1060 |
サービスが存在しない |
| reg query |
0 |
レジストリキーが見つかった |
| 1 |
レジストリキーが見つからない |
遅延展開とERRORLEVEL ── 括弧ブロック・FORループでの判定
バッチファイルでは、if や for の括弧ブロック ( ... ) 内で %ERRORLEVEL% を参照すると、ブロックの実行前に値が確定(展開)されてしまうことがあります。これは「変数の即時展開」と呼ばれる仕様で、ブロック内でコマンドを実行してもERRORLEVELの値が更新されないように見えます。
問題: 括弧ブロック内での%ERRORLEVEL%
NG: 括弧ブロック内で%ERRORLEVEL%が固定される
@echo off
if 1==1 (
rem このブロック全体が「解析時」に%ERRORLEVEL%を展開
copy notexist.txt dest.txt >nul 2>&1
echo ERRORLEVEL = %ERRORLEVEL%
rem ↑ copyが失敗しても 0 と表示される場合がある
)
この問題が起こる理由は、cmd.exe がバッチファイルを行単位ではなくブロック単位で解析するためです。括弧ブロック全体が1つの処理単位として扱われ、ブロック内の %変数% はブロックの実行前にすべて展開(値に置換)されます。
解決策: 遅延展開(enabledelayedexpansion)
遅延展開を有効にすると、!変数! という書式で実行時に値を評価できるようになります。
OK: 遅延展開で正しく判定
@echo off
setlocal enabledelayedexpansion
if 1==1 (
copy notexist.txt dest.txt >nul 2>&1
echo ERRORLEVEL = !ERRORLEVEL!
rem ↑ 正しく 1 と表示される
)
FORループ内でのエラー判定
FORループの本体も括弧ブロックなので、同じ問題が発生します。ループ内で各コマンドのエラーを判定するには遅延展開が必須です。
FORループ内のエラー判定
@echo off
setlocal enabledelayedexpansion
set ERROR_COUNT=0
for %%F in (file1.txt file2.txt file3.txt) do (
copy %%F backup\ >nul 2>&1
if !ERRORLEVEL! neq 0 (
echo [ERROR] %%F のコピーに失敗
set /a ERROR_COUNT+=1
) else (
echo [OK] %%F のコピーに成功
)
)
echo エラー件数: !ERROR_COUNT!
if !ERROR_COUNT! gtr 0 exit /b 1
遅延展開の代替手段: if errorlevel
if errorlevel N 構文は、%ERRORLEVEL% と異なり括弧ブロック内でも常に最新の値を参照できます。遅延展開を使えない状況では if errorlevel を代替として使うことができます。
if errorlevel は括弧内でも正しく動作する
@echo off
rem 遅延展開なしでもOK
for %%F in (file1.txt file2.txt file3.txt) do (
copy %%F backup\ >nul 2>&1
if errorlevel 1 (
echo [ERROR] %%F のコピーに失敗
) else (
echo [OK] %%F のコピーに成功
)
)
| 方法 |
括弧ブロック内 |
要遅延展開 |
等号比較 |
| %ERRORLEVEL% |
値が固定される |
必要 |
可能 |
| !ERRORLEVEL! |
最新値を参照 |
使用中 |
可能 |
| if errorlevel N |
最新値を参照 |
不要 |
不可(N以上判定のみ) |
パイプ処理とERRORLEVEL
パイプ(|)でコマンドを連結した場合、ERRORLEVELは最後のコマンドの終了コードになります。パイプの前段が失敗しても、後段が成功すればERRORLEVELは0になってしまいます。
NG: パイプで前段のエラーを見逃す
@echo off
rem typeが失敗しても、findが成功すればERRORLEVEL=0
type notexist.txt | find "ABC"
echo ERRORLEVEL = %ERRORLEVEL%
rem ↑ typeの失敗は検知できない
解決策: パイプを分解して個別に判定
OK: パイプを分解して個別にエラー判定
@echo off
rem ステップ1: ファイル読み取りの確認
type sample.txt >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo [ERROR] ファイルの読み取りに失敗しました
exit /b 1
)
rem ステップ2: 検索の実行
find "ABC" sample.txt >nul
if %ERRORLEVEL% equ 0 (
echo 見つかりました
) else if %ERRORLEVEL% equ 1 (
echo 見つかりませんでした
) else (
echo [ERROR] 検索処理でエラーが発生しました
exit /b 2
)
サブルーチンとERRORLEVEL
call :label でサブルーチンを呼び出す場合、サブルーチンの exit /b N で返した値がERRORLEVELとして呼び出し元に伝わります。サブルーチンの末尾で exit /b を明示的に記述しないと、意図しない終了コードが残る可能性があります。
サブルーチンの戻り値設計
@echo off
rem サブルーチンの呼び出しと戻り値の判定
call :check_file "C:\data\input.csv"
if %ERRORLEVEL% neq 0 (
echo ファイルチェックに失敗しました
exit /b 1
)
call :process_file "C:\data\input.csv"
if %ERRORLEVEL% neq 0 (
echo ファイル処理に失敗しました
exit /b 1
)
echo すべての処理が完了しました
exit /b 0
rem ===== サブルーチン =====
:check_file
if not exist %~1 (
echo [ERROR] ファイルが見つかりません: %~1
exit /b 1
)
echo [OK] ファイル存在確認: %~1
exit /b 0
:process_file
echo 処理中: %~1
rem 何らかの処理...
findstr "ERROR" %~1 >nul 2>&1
if %ERRORLEVEL% equ 0 (
echo [WARN] ファイルにエラーが含まれています
exit /b 2
)
exit /b 0
注意:exit /b と exit は異なります。exit(/bなし)はcmd.exeそのものを終了させるため、バッチファイルの呼び出し元や、タスクスケジューラから起動された場合にウィンドウが閉じてしまいます。サブルーチンからの戻りには必ず exit /b を使いましょう。
実務で使えるエラー判定パターン集
ここからは、実務で頻繁に遭遇する具体的なエラー判定パターンを紹介します。そのまま流用できるテンプレートとして活用してください。
パターン1: ファイルコピーとバックアップ
業務バッチで最も基本的なパターンです。コピー元の存在確認から、コピー実行、結果の検証まで含めます。
ファイルコピー with エラー判定
@echo off
setlocal
set SRC=C:\data\daily_report.csv
set DST=D:\backup\daily_report_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.csv
rem コピー元の存在確認
if not exist "%SRC%" (
echo [ERROR] コピー元ファイルが存在しません: %SRC%
exit /b 1
)
rem コピー先ディレクトリの確認・作成
if not exist "D:\backup" (
mkdir "D:\backup"
if %ERRORLEVEL% neq 0 (
echo [ERROR] バックアップディレクトリの作成に失敗
exit /b 1
)
)
rem ファイルコピーの実行
copy /Y "%SRC%" "%DST%" >nul
if %ERRORLEVEL% neq 0 (
echo [ERROR] ファイルコピーに失敗: %SRC% → %DST%
exit /b 1
)
rem コピー結果の検証
if not exist "%DST%" (
echo [ERROR] コピー先にファイルが存在しません
exit /b 1
)
echo [OK] バックアップ完了: %DST%
exit /b 0
パターン2: robocopyによるディレクトリ同期
robocopyディレクトリ同期のエラー判定
@echo off
setlocal
set SRC=C:\project\release
set DST=\\server\share\deploy
set LOG=C:\logs\robocopy_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log
robocopy "%SRC%" "%DST%" /MIR /R:3 /W:10 /LOG:"%LOG%" /NP
set RC=%ERRORLEVEL%
rem robocopy: 0〜7=正常、8以上=エラー
if %RC% geq 8 (
echo [ERROR] robocopy失敗 コード:%RC% ログ:%LOG%
exit /b 1
)
if %RC% equ 0 (
echo [INFO] 変更なし
) else (
echo [OK] 同期完了(コード:%RC%)
)
exit /b 0
パターン3: ネットワーク接続の確認
ネットワーク接続確認バッチ
@echo off
setlocal enabledelayedexpansion
set TARGETS=dbserver fileserver webserver
set FAIL_COUNT=0
for %%H in (%TARGETS%) do (
ping -n 1 -w 3000 %%H >nul 2>&1
if !ERRORLEVEL! neq 0 (
echo [NG] %%H に接続できません
set /a FAIL_COUNT+=1
) else (
echo [OK] %%H 接続確認
)
)
if !FAIL_COUNT! gtr 0 (
echo [ERROR] !FAIL_COUNT! 台のサーバーに接続できませんでした
exit /b 1
)
echo [OK] すべてのサーバーに接続確認完了
exit /b 0
パターン4: Windowsサービスの状態確認
サービスの状態確認と自動復旧
@echo off
setlocal
set SERVICE_NAME=MySQL80
rem サービスの存在確認
sc query %SERVICE_NAME% >nul 2>&1
if %ERRORLEVEL% equ 1060 (
echo [ERROR] サービス %SERVICE_NAME% は存在しません
exit /b 1
)
rem サービスの状態確認(RUNNINGかどうか)
sc query %SERVICE_NAME% | findstr "RUNNING" >nul
if %ERRORLEVEL% neq 0 (
echo [WARN] %SERVICE_NAME% が停止しています。起動を試みます...
net start %SERVICE_NAME%
if %ERRORLEVEL% neq 0 (
echo [ERROR] %SERVICE_NAME% の起動に失敗しました
exit /b 1
)
echo [OK] %SERVICE_NAME% を起動しました
) else (
echo [OK] %SERVICE_NAME% は正常に稼働中です
)
exit /b 0
パターン5: データベース接続確認(sqlcmd)
SQL Serverへの接続確認
@echo off
setlocal
set DB_SERVER=localhost\SQLEXPRESS
set DB_NAME=MyDatabase
set MAX_RETRY=3
set RETRY_WAIT=5
for /L %%I in (1,1,%MAX_RETRY%) do (
echo 接続試行 %%I/%MAX_RETRY% ...
sqlcmd -S %DB_SERVER% -d %DB_NAME% -Q "SELECT 1" -h -1 >nul 2>&1
if !ERRORLEVEL! equ 0 (
echo [OK] データベース接続成功
exit /b 0
)
echo [WARN] 接続失敗。%RETRY_WAIT%秒後にリトライ...
timeout /t %RETRY_WAIT% /nobreak >nul
)
echo [ERROR] %MAX_RETRY%回の試行後もDB接続に失敗しました
exit /b 1
ポイント:DB接続やネットワーク系の処理は、一時的な障害で失敗することがあるためリトライ機能を組み込むのが実務の定石です。上記のように for /L とtimeoutを組み合わせれば、指定回数のリトライを簡単に実装できます。
パターン6: 複数処理の一括実行と結果サマリー
複数のタスクを実行し、最後に結果をまとめて表示するパターンです。業務の日次バッチなどで活用できます。
複数タスクの一括実行と結果サマリー
@echo off
setlocal enabledelayedexpansion
set TOTAL=0
set SUCCESS=0
set FAIL=0
rem --- タスク1: ファイルコピー ---
set /a TOTAL+=1
copy data.csv backup\ >nul 2>&1
if !ERRORLEVEL! equ 0 (
echo [OK] タスク1: ファイルコピー
set /a SUCCESS+=1
) else (
echo [NG] タスク1: ファイルコピー
set /a FAIL+=1
)
rem --- タスク2: ログクリーンアップ ---
set /a TOTAL+=1
forfiles /P C:\logs /M *.log /D -30 /C "cmd /c del @path" 2>nul
if !ERRORLEVEL! leq 1 (
echo [OK] タスク2: ログクリーンアップ
set /a SUCCESS+=1
) else (
echo [NG] タスク2: ログクリーンアップ
set /a FAIL+=1
)
rem --- タスク3: レポート生成 ---
set /a TOTAL+=1
cscript //nologo generate_report.vbs
if !ERRORLEVEL! equ 0 (
echo [OK] タスク3: レポート生成
set /a SUCCESS+=1
) else (
echo [NG] タスク3: レポート生成
set /a FAIL+=1
)
rem --- 結果サマリー ---
echo ========================================
echo 実行結果: !SUCCESS!/!TOTAL! 成功, !FAIL! 失敗
echo ========================================
if !FAIL! gtr 0 exit /b 1
exit /b 0
エラーログの出力 ── 記録と通知の仕組み
エラーが発生したことを判定するだけでなく、いつ・どこで・何が起きたかを記録に残すことが運用では欠かせません。ここではバッチファイルでのエラーログ出力のパターンを紹介します。
基本: ログファイルへの出力
タイムスタンプ付きログ出力
@echo off
setlocal
set LOG_FILE=C:\logs\batch_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log
rem ログ出力用のサブルーチンを定義
call :log INFO "バッチ処理を開始します"
copy data.csv backup\ >nul 2>&1
if %ERRORLEVEL% neq 0 (
call :log ERROR "ファイルコピーに失敗(code: %ERRORLEVEL%)"
exit /b 1
)
call :log INFO "ファイルコピー完了"
call :log INFO "バッチ処理が正常に完了しました"
exit /b 0
:log
set LEVEL=%~1
set MSG=%~2
echo %DATE% %TIME% [%LEVEL%] %MSG%
echo %DATE% %TIME% [%LEVEL%] %MSG% >> "%LOG_FILE%"
exit /b 0
ログ出力例
2026/03/06 10:30:15.42 [INFO] バッチ処理を開始します
2026/03/06 10:30:15.89 [INFO] ファイルコピー完了
2026/03/06 10:30:16.01 [INFO] バッチ処理が正常に完了しました
標準出力と標準エラー出力の分離
コマンドの出力は、標準出力(stdout: ファイル記述子1)と標準エラー出力(stderr: ファイル記述子2)に分かれます。エラーログを正確に取るには、この2つを適切にリダイレクトします。
| リダイレクト |
意味 |
用途 |
> file.log |
標準出力をファイルに書き込み(上書き) |
通常の出力ログ |
>> file.log |
標準出力をファイルに追記 |
ログの蓄積 |
2> error.log |
標準エラー出力をファイルに書き込み |
エラーだけ分離 |
2>> error.log |
標準エラー出力をファイルに追記 |
エラーログの蓄積 |
2>&1 |
標準エラーを標準出力にマージ |
全出力を1ファイルに |
>nul 2>&1 |
すべての出力を破棄 |
出力を完全に抑制 |
標準出力と標準エラーを分離してログ
@echo off
rem 通常出力は output.log、エラーは error.log に分離
robocopy C:\src C:\dst /MIR > output.log 2> error.log
rem 全出力をまとめて1つのログファイルに記録
robocopy C:\src C:\dst /MIR >> all.log 2>&1
ログローテーション
ログファイルが無制限に増え続けないよう、古いログを自動で削除する仕組みを組み込みます。
30日以上前のログを自動削除
@echo off
rem 30日以上前の.logファイルを削除
forfiles /P C:\logs /M *.log /D -30 /C "cmd /c echo 削除: @path && del @path" 2>nul
rem forfilesは対象ファイルがない場合もERRORLEVEL=1を返す
rem そのためエラー判定は不要(対象なし=正常)
よくあるミスとトラブルシューティング
ERRORLEVELの判定で陥りやすいミスと、その対処法をまとめます。
ミス1: echoやsetをエラー判定の前に挟む
最も多いミスです。デバッグ用の echo を判定前に入れると、echoの終了コード(常に0)でERRORLEVELが上書きされます。
NG: echoがERRORLEVELを上書き
copy notexist.txt dest.txt
echo コピー処理が終わりました rem ← これでERRORLEVELが0になる
if %ERRORLEVEL% neq 0 echo エラー rem ← 検知できない
OK: 判定を先にする、または変数に保存
rem 方法1: 判定を先にする
copy notexist.txt dest.txt
if %ERRORLEVEL% neq 0 (
echo [ERROR] コピー失敗
exit /b 1
)
echo コピー完了
rem 方法2: 変数に保存する
copy notexist.txt dest.txt
set COPY_RESULT=%ERRORLEVEL%
echo コピー処理が終わりました
if %COPY_RESULT% neq 0 (
echo [ERROR] コピー失敗(code: %COPY_RESULT%)
exit /b 1
)
ミス2: set ERRORLEVEL=値 でシステム値を上書き
set ERRORLEVEL=0 のようにERRORLEVELという名前の環境変数を定義すると、システムのERRORLEVELが参照できなくなります。以降すべての %ERRORLEVEL% がユーザー定義の値を返すため、エラー判定が完全に壊れます。
NG: set ERRORLEVEL でシステム値を破壊
rem これをやると以降の%ERRORLEVEL%が常に0になる
set ERRORLEVEL=0 rem ← 絶対にやってはいけない
rem 修正するには値なしでsetする
set ERRORLEVEL= rem ← ユーザー定義を削除してシステム値に戻す
ミス3: if errorlevel の「以上」判定を忘れる
小さい値から順にチェックすると、常に最初の条件に一致してしまいます。
NG: 小さい値からチェック
rem ERRORLEVELが3の場合、「1以上」に一致してしまう
if errorlevel 1 echo コード1 rem ← 3でもここに入る
if errorlevel 2 echo コード2
if errorlevel 3 echo コード3 rem ← ここには到達しない
ミス4: exit と exit /b の混同
| コマンド |
動作 |
使い分け |
exit /b N |
現在のバッチファイル/サブルーチンを終了し、終了コードNを返す |
通常はこちらを使う |
exit N |
cmd.exeプロセスそのものを終了させる |
コマンドプロンプトが閉じるので注意 |
exit /b |
終了コードを指定せず終了(直前のERRORLEVELがそのまま残る) |
明示的にNを指定する方が安全 |
ミス5: ERRORLEVELをリセットしようとする
ERRORLEVELを明示的に0にリセットしたい場合、set ERRORLEVEL=0 はNGです(ミス2参照)。正しい方法は以下のとおりです。
ERRORLEVELの正しいリセット方法
rem 方法1: 必ず成功するコマンドを実行する
ver >nul rem ver は常にERRORLEVEL=0を返す
rem 方法2: cmd /c exit 0 を実行する
cmd /c "exit /b 0"
rem NG: これはユーザー変数を定義するだけ
rem set ERRORLEVEL=0 ← やってはいけない
実務テンプレート: エラーハンドリング付きバッチの基本構造
ここまでの内容を総合した、実務で使えるバッチファイルのテンプレートを紹介します。エラー判定・ログ出力・戻り値設計がすべて組み込まれた構造です。
実務テンプレート: エラーハンドリング付きバッチ
@echo off
setlocal enabledelayedexpansion
rem ===== 設定 =====
set SCRIPT_NAME=%~n0
set LOG_DIR=C:\logs
set LOG_FILE=%LOG_DIR%\%SCRIPT_NAME%_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log
set EXIT_CODE=0
rem ===== ログディレクトリの確認 =====
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
call :log INFO "%SCRIPT_NAME% 開始"
rem ===== メイン処理 =====
call :task1 || goto :error_exit
call :task2 || goto :error_exit
call :task3 || goto :error_exit
rem ===== 正常終了 =====
call :log INFO "%SCRIPT_NAME% 正常終了"
exit /b 0
rem ===== エラー終了 =====
:error_exit
set EXIT_CODE=%ERRORLEVEL%
call :log ERROR "%SCRIPT_NAME% 異常終了(code: !EXIT_CODE!)"
exit /b !EXIT_CODE!
rem ===== サブルーチン =====
:task1
call :log INFO "タスク1: ファイルバックアップ開始"
copy /Y data.csv backup\ >nul 2>&1
if !ERRORLEVEL! neq 0 (
call :log ERROR "タスク1: ファイルバックアップ失敗"
exit /b 1
)
call :log INFO "タスク1: 完了"
exit /b 0
:task2
call :log INFO "タスク2: データ処理開始"
rem 処理内容をここに記述...
call :log INFO "タスク2: 完了"
exit /b 0
:task3
call :log INFO "タスク3: レポート送信開始"
rem 処理内容をここに記述...
call :log INFO "タスク3: 完了"
exit /b 0
:log
set _LV=%~1
set _MSG=%~2
echo %DATE% %TIME% [%_LV%] %_MSG%
echo %DATE% %TIME% [%_LV%] %_MSG% >> "%LOG_FILE%"
exit /b 0
ポイント:このテンプレートには3つの重要な設計が含まれています。(1) call :task || goto :error_exit で失敗時に即座にエラー終了、(2) ログ出力サブルーチンによる一貫したログ記録、(3) 各サブルーチンで明示的に exit /b 0 または exit /b 1 を返す戻り値設計です。
エラー判定の方法比較 ── 状況別の選び方
最後に、本記事で紹介したエラー判定方法を状況別に整理します。
| 状況 |
推奨方法 |
理由 |
| 単純な成功/失敗の判定 |
if %ERRORLEVEL% neq 0 |
直感的で最も一般的 |
| 1行で簡潔に書きたい |
command || exit /b 1 |
最もコンパクト |
| 特定の終了コードで分岐 |
if %ERRORLEVEL% equ N |
等号比較が可能 |
| 括弧ブロック/FORループ内 |
if !ERRORLEVEL! neq 0 |
遅延展開で正しく判定 |
| 遅延展開を使えない環境 |
if errorlevel 1 |
括弧内でも最新値を参照 |
| robocopyの判定 |
if %ERRORLEVEL% geq 8 |
0〜7は正常のため |
| 連続コマンドの安全実行 |
cmd1 && cmd2 && cmd3 |
途中で失敗したら停止 |
まとめ
バッチファイルでエラーが発生したかを判定する方法について、基本から実務レベルまで解説しました。重要なポイントを振り返ります。
この記事のまとめ
- ERRORLEVELは直前のコマンドの終了コードで上書きされる。蓄積はされない
%ERRORLEVEL% は等号比較が可能だが、括弧ブロック内では遅延展開(!ERRORLEVEL!)が必要
if errorlevel N は「N以上」の判定。大きい値から順にチェックする
||(失敗時実行)と &&(成功時実行)で簡潔なエラー処理が可能
- robocopyは0〜7が正常、find/findstrは1が「見つからない」。コマンドごとに終了コードの意味が異なる
- エラー判定は対象コマンドの直後で行う。echoやsetを挟まない
set ERRORLEVEL=値 はシステム値を破壊するので絶対にやらない
- サブルーチンでは
exit /b N で明示的に戻り値を返す
ERRORLEVELを「なんとなく見る」のではなく、どのコマンドの結果をいつ・どのように判定するかを設計することで、実運用に耐えるエラーハンドリングが実現します。
バッチファイルのエラーハンドリング関連記事
エラー処理についてさらに詳しく知りたい方は、以下の記事もあわせてご覧ください。