バッチファイルのサブルーチンから呼び出し元に戻るとき、exit /b を正しく使えているでしょうか。exit と誤って書いてコマンドプロンプトごと閉じてしまった、終了コードを指定し忘れて呼び出し元のエラー判定が狂った、サブルーチン末尾に書き忘れて処理が次のラベルに流れた――こうしたトラブルは現場でよく起きます。本記事では、exit /b の仕組みを根本から理解したうえで、よくある誤りパターンとその修正方法を体系的に解説します。
- exit / exit /b / goto :eof の違いを理解する
- call コンテキストと exit /b の関係
- よくある誤りパターン①:exit を使ってしまう
- よくある誤りパターン②:終了コードを指定しない
- よくある誤りパターン③:サブルーチン末尾に exit /b を書き忘れる
- よくある誤りパターン④:for ループ・if ブロック内での exit /b
- よくある誤りパターン⑤:setlocal と exit /b の組み合わせ
- よくある誤りパターン⑥:call なしのサブルーチンで exit /b を使う
- goto :eof との使い分け
- 終了コードの設計パターン
- 実践テンプレート:正しいサブルーチン構造
- よくある誤りパターン一覧表
- よくある質問
- まとめ
exit / exit /b / goto :eof の違いを理解する
まず 3 つのコマンドの動作を整理します。どれを使うかで挙動が大きく変わります。
| コマンド | 動作 | cmd.exe は閉じる? | 終了コード |
|---|---|---|---|
exit |
cmd.exe プロセスごと終了 | ○(閉じる) | 0(既定) |
exit N |
cmd.exe プロセスごと終了 | ○(閉じる) | N |
exit /b |
現在のバッチスクリプトを終了し呼び出し元へ戻る | ×(残る) | 直前のERRORLEVEL |
exit /b N |
現在のバッチスクリプトを終了し呼び出し元へ戻る | ×(残る) | N |
goto :eof |
バッチ末尾(暗黙ラベル)にジャンプして終了 | ×(残る) | 変更しない |
exit /b は「現在のスクリプトコンテキスト」を終了します。call で呼ばれたサブルーチンならそのサブルーチンだけ終了し、呼び出し元のバッチに制御が戻ります。トップレベルのバッチで exit /b を使うと、そのバッチ自体が終了します。
call コンテキストと exit /b の関係
cmd.exe はバッチ実行のコンテキストをスタック管理しています。call :label でサブルーチンに入るとスタックに積まれ、exit /b でスタックから取り出されて呼び出し元に戻ります。
@echo off call :sub echo 呼び出し元に戻ってきた (ERRORLEVEL=%ERRORLEVEL%) exit /b 0 :sub echo サブルーチン実行中 exit /b 0
call なしで goto :label した場合は「同じコンテキスト内のジャンプ」なので、exit /b を使うとそのバッチ全体が終了します。
よくある誤りパターン①:exit を使ってしまう
サブルーチンから戻るつもりで exit(/b なし)と書くと、cmd.exe ウィンドウごと閉じます。タスクスケジューラからの実行でも同様に問題が起きます。
@echo off call :validate echo 検証完了後の処理 exit /b 0 :validate echo 検証中... exit rem 誤り: cmd.exe ごと終了してしまう
@echo off call :validate echo 検証完了後の処理 exit /b 0 :validate echo 検証中... exit /b 0 rem 正しい: 呼び出し元に戻る
よくある誤りパターン②:終了コードを指定しない
exit /b のみ(数値なし)で書くと、直前のコマンドの ERRORLEVEL がそのまま呼び出し元に伝播します。サブルーチン内で失敗したコマンドがあると、意図せずエラーコードが呼び出し元に返ります。
@echo off call :check_file if errorlevel 1 echo エラー検出! exit /b 0 :check_file ping -n 1 192.168.99.1 >nul 2>nul rem 失敗するがエラーを無視したい echo チェック完了 exit /b rem 誤り: ping の ERRORLEVEL=1 が呼び出し元に伝わる
@echo off call :check_file if errorlevel 1 echo エラー検出! exit /b 0 :check_file ping -n 1 192.168.99.1 >nul 2>nul rem 失敗しても続行 echo チェック完了 exit /b 0 rem 正しい: 正常終了を明示
よくある誤りパターン③:サブルーチン末尾に exit /b を書き忘れる
サブルーチンの末尾に exit /b がないと、処理が次のラベルへ「流れ落ち」て続きのコードが実行されます。
@echo off call :step1 echo step1 完了 exit /b 0 :step1 echo step1 実行中 rem exit /b を忘れた! :step2 echo step2 も実行されてしまう(意図しない)
@echo off call :step1 echo step1 完了 exit /b 0 :step1 echo step1 実行中 exit /b 0 rem 必須: ここで呼び出し元に戻る :step2 echo step2 は呼ばれたときだけ実行される exit /b 0
よくある誤りパターン④:for ループ・if ブロック内での exit /b
for や if のブロック(括弧内)で exit /b を使うと、ループのその回だけでなくバッチスクリプト全体が終了します。「ループを途中で抜けたい」という意図でも、実際にはバッチそのものが終わります。
@echo off
for %%F in (a.txt b.txt c.txt) do (
if not exist "%%F" (
echo %%F が見つかりません
exit /b 1 rem ループを抜けるつもりが、バッチ全体が終了
)
echo %%F を処理中
)
echo 全ファイル処理完了
ループを途中で抜けたい場合は、フラグ変数を使って後続処理をスキップする方法が現実的です。
@echo off
setlocal EnableDelayedExpansion
set "ABORT=0"
for %%F in (a.txt b.txt c.txt) do (
if "!ABORT!"=="1" (
rem スキップ
) else (
if not exist "%%F" (
echo %%F が見つかりません
set "ABORT=1"
) else (
echo %%F を処理中
)
)
)
if "!ABORT!"=="1" (
echo エラーのため中断しました
endlocal & exit /b 1
)
echo 全ファイル処理完了
endlocal
exit /b 0
よくある誤りパターン⑤:setlocal と exit /b の組み合わせ
setlocal した状態で exit /b を呼ぶと、cmd.exe は自動的に endlocal を実行してスコープを閉じます。問題は「サブルーチン内で変数に計算結果を入れて呼び出し元に渡したい」場合です。exit /b の前に endlocal すると変数が消えてしまいます。
@echo off call :calc 10 20 echo 結果: %RESULT% exit /b 0 :calc setlocal set /a ANS=%1+%2 set "RESULT=%ANS%" endlocal rem RESULT が消える exit /b 0
@echo off call :calc 10 20 echo 結果: %RESULT% exit /b 0 :calc setlocal set /a ANS=%1+%2 endlocal & set "RESULT=%ANS%" rem endlocal と set を同一行に書く exit /b 0
endlocal & set "VAR=値" と 1 行にまとめることで、endlocal 後に変数が親スコープにセットされます。
よくある誤りパターン⑥:call なしのサブルーチンで exit /b を使う
goto :label でジャンプした先で exit /b を使うと、「同じコンテキスト内のジャンプ」なのでバッチ全体が終了します。この場合は goto で元に戻るか、call :label に変更する必要があります。
@echo off goto :process echo ここには戻らない exit /b 0 :process echo 処理中 exit /b 0 rem 誤り: バッチ全体が終了し「戻る」場所がない
@echo off call :process echo 処理後に戻ってきた exit /b 0 :process echo 処理中 exit /b 0 rem 正しい: call した場所に戻る
goto :eof との使い分け
goto :eof は暗黙的なバッチ末尾ラベル(:eof)へジャンプし、スクリプトを終了します。exit /b とほぼ同等ですが、違いがあります。
| 比較項目 | exit /b | goto :eof |
|---|---|---|
| 終了コードの変更 | ○(exit /b N で設定) |
×(ERRORLEVELをそのまま維持) |
| 記述の簡潔さ | 普通 | 短い |
| サブルーチン末尾での動作 | 呼び出し元に戻る | 呼び出し元に戻る |
| 明示的なコード指定 | できる | できない |
@echo off call :sub echo 戻ってきた goto :eof rem バッチ末尾に到達 = 終了 :sub echo サブルーチン goto :eof rem call した場所に戻る
終了コードを明示したい場合は exit /b 0 や exit /b 1 を使い、終了コードを変えたくない場合は goto :eof が便利です。
終了コードの設計パターン
呼び出し元でエラーを判定できるよう、終了コードを一貫して設計します。
@echo off
call :backup "C:\data" "D:\bk"
if errorlevel 2 (
echo 致命的エラー: バックアップ中断
exit /b 2
)
if errorlevel 1 (
echo 警告: 一部ファイルのコピーに失敗
)
echo バックアップ完了
exit /b 0
:backup
set "SRC=%~1"
set "DST=%~2"
if not exist "%SRC%" (
echo コピー元が見つかりません: %SRC%
exit /b 2 rem 致命的エラー
)
mkdir "%DST%" 2>nul
xcopy "%SRC%" "%DST%" /E /Y >nul 2>nul
if errorlevel 1 (
echo 一部ファイルのコピー失敗
exit /b 1 rem 警告レベル
)
exit /b 0
実践テンプレート:正しいサブルーチン構造
これまでの要点をすべて組み込んだ、エラーハンドリング付きの推奨サブルーチン構造です。
@echo off
setlocal EnableExtensions EnableDelayedExpansion
rem ===== メイン処理 =====
call :check_env
if errorlevel 1 (
echo 環境チェック失敗
endlocal & exit /b 1
)
call :process "C:\data"
if errorlevel 1 (
echo 処理失敗
endlocal & exit /b 1
)
echo すべての処理が完了しました
endlocal & exit /b 0
rem ===== サブルーチン =====
:check_env
setlocal
if not exist "C:\data" (
echo エラー: C:\data が存在しません
endlocal & exit /b 1
)
echo 環境チェック OK
endlocal & exit /b 0
:process
setlocal
set "TARGET=%~1"
if not defined TARGET (
echo エラー: 引数が不足しています
endlocal & exit /b 1
)
echo %TARGET% を処理中...
rem 実際の処理をここに記述
endlocal & exit /b 0
よくある誤りパターン一覧表
| 誤りパターン | 症状 | 修正方法 |
|---|---|---|
exit をサブルーチン末尾に書く |
cmd.exe ウィンドウが閉じる | exit /b 0 に変更 |
exit /b で数値を指定しない |
直前の ERRORLEVEL が呼び出し元に伝播 | exit /b 0 / exit /b 1 と明示 |
サブルーチン末尾に exit /b がない |
次のラベルのコードも実行される | すべてのサブルーチン末尾に追加 |
for ループ内で exit /b |
ループを抜けるつもりがバッチ全体終了 | フラグ変数でループ後に判定 |
goto で飛んだ先で exit /b |
「戻る」場所がなくバッチ終了 | goto を call に変更 |
| setlocal 後に結果を変数で渡せない | endlocal で変数が消える | endlocal & set "VAR=値" を使う |
よくある質問
exit /b でバッチが終了すると、その cmd.exe も終了してウィンドウが閉じます。これは exit と同じ見た目になります。exit /b N、そうでない場合は goto :eof を使うのが一般的です。チーム内でどちらかに統一するとコードが読みやすくなります。if errorlevel 2 は「2以上」を意味するため、複数レベルを判定するときは大きい値から先にチェックします。exit /b 1 で終了すると、タスクの「最終実行結果」欄が 0x1 になりエラーとして扱われます。正常終了は必ず exit /b 0 で明示しましょう。まとめ
exit /b に関する問題の大半は、次の 3 点を守れば防げます。
- サブルーチン末尾には必ず
exit /b 0(または適切なコード)を書く /bを忘れない。exitだけ書くと cmd.exe ごと終了する- 終了コードは必ず明示する。
exit /b(数値なし)は直前の ERRORLEVEL を伝播させる
より詳しい使い方はEXIT /B 完全解説を、サブルーチンの呼び出し構造についてはラベル・GOTO・CALL 完全ガイドをあわせて参照してください。

