bat exit /b 完全攻略|exit との違い・終了コード設計・サブルーチン設計・goto :eof・よくある誤りパターンまで徹底解説

【bat】exit /b の使い方を誤って予期せぬ終了になるときの修正方法 bat

バッチファイルのサブルーチンから呼び出し元に戻るとき、exit /b を正しく使えているでしょうか。exit と誤って書いてコマンドプロンプトごと閉じてしまった、終了コードを指定し忘れて呼び出し元のエラー判定が狂った、サブルーチン末尾に書き忘れて処理が次のラベルに流れた――こうしたトラブルは現場でよく起きます。本記事では、exit /b の仕組みを根本から理解したうえで、よくある誤りパターンとその修正方法を体系的に解説します。

スポンサーリンク

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 でスタックから取り出されて呼び出し元に戻ります。

call と 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 ウィンドウごと閉じます。タスクスケジューラからの実行でも同様に問題が起きます。

NG: exit でウィンドウが閉じる
@echo off
call :validate
echo 検証完了後の処理
exit /b 0

:validate
echo 検証中...
exit          rem 誤り: cmd.exe ごと終了してしまう
OK: exit /b で呼び出し元に戻る
@echo off
call :validate
echo 検証完了後の処理
exit /b 0

:validate
echo 検証中...
exit /b 0     rem 正しい: 呼び出し元に戻る

よくある誤りパターン②:終了コードを指定しない

exit /b のみ(数値なし)で書くと、直前のコマンドの ERRORLEVEL がそのまま呼び出し元に伝播します。サブルーチン内で失敗したコマンドがあると、意図せずエラーコードが呼び出し元に返ります。

NG: 終了コードが意図せず伝播
@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 が呼び出し元に伝わる
OK: 終了コードを明示する
@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 がないと、処理が次のラベルへ「流れ落ち」て続きのコードが実行されます。

NG: exit /b がなく次のラベルに流れる
@echo off
call :step1
echo step1 完了
exit /b 0

:step1
echo step1 実行中
rem exit /b を忘れた!

:step2
echo step2 も実行されてしまう(意図しない)
OK: 各サブルーチンを exit /b で閉じる
@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

forif のブロック(括弧内)で exit /b を使うと、ループのその回だけでなくバッチスクリプト全体が終了します。「ループを途中で抜けたい」という意図でも、実際にはバッチそのものが終わります。

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 全ファイル処理完了

ループを途中で抜けたい場合は、フラグ変数を使って後続処理をスキップする方法が現実的です。

OK: フラグ変数でループを擬似的に抜ける
@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 すると変数が消えてしまいます。

NG: 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
OK: endlocal & set で変数を引き渡す
@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 に変更する必要があります。

NG: goto で飛んだ先で exit /b を使う
@echo off
goto :process
echo ここには戻らない
exit /b 0

:process
echo 処理中
exit /b 0   rem 誤り: バッチ全体が終了し「戻る」場所がない
OK: call で呼び出して exit /b で戻る
@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をそのまま維持)
記述の簡潔さ 普通 短い
サブルーチン末尾での動作 呼び出し元に戻る 呼び出し元に戻る
明示的なコード指定 できる できない
goto :eof の使い方
@echo off
call :sub
echo 戻ってきた
goto :eof    rem バッチ末尾に到達 = 終了

:sub
echo サブルーチン
goto :eof    rem call した場所に戻る

終了コードを明示したい場合は exit /b 0exit /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
終了コードの慣習:0 = 正常終了、1 = 警告や軽微なエラー、2 以上 = 致命的エラーとする設計が多いです。チーム開発ではコードの意味を README やコメントに明記しておきましょう。

実践テンプレート:正しいサブルーチン構造

これまでの要点をすべて組み込んだ、エラーハンドリング付きの推奨サブルーチン構造です。

推奨サブルーチン構造テンプレート
@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 「戻る」場所がなくバッチ終了 gotocall に変更
setlocal 後に結果を変数で渡せない endlocal で変数が消える endlocal & set "VAR=値" を使う

よくある質問

Q. exit /b の /b は何の略ですか?
A. /b は “batch” の略です。バッチスクリプトのコンテキストのみを終了し、コマンドプロンプトウィンドウは閉じないという意味です。
Q. バッチファイルをダブルクリックで実行した場合、exit /b を使うとウィンドウはどうなりますか?
A. バッチファイルをダブルクリックで実行すると、cmd.exe が起動してバッチを実行します。exit /b でバッチが終了すると、その cmd.exe も終了してウィンドウが閉じます。これは exit と同じ見た目になります。
Q. goto :eof と exit /b はどちらを使うべきですか?
A. 終了コードを明示したい場合は exit /b N、そうでない場合は goto :eof を使うのが一般的です。チーム内でどちらかに統一するとコードが読みやすくなります。
Q. exit /b 1 と exit /b 2 の違いは何ですか?
A. どちらも呼び出し元の ERRORLEVEL に設定される値です。具体的な意味はスクリプト設計者が決めます。慣習として 0=正常、1=警告、2以上=致命的エラーとする設計が多いです。if errorlevel 2 は「2以上」を意味するため、複数レベルを判定するときは大きい値から先にチェックします。
Q. タスクスケジューラから実行したバッチで exit /b 1 を返すと、スケジューラ側でエラーとして扱われますか?
A. はい、タスクスケジューラはバッチの終了コード(ERRORLEVEL)を「最終結果」として記録します。exit /b 1 で終了すると、タスクの「最終実行結果」欄が 0x1 になりエラーとして扱われます。正常終了は必ず exit /b 0 で明示しましょう。

まとめ

exit /b に関する問題の大半は、次の 3 点を守れば防げます。

  • サブルーチン末尾には必ず exit /b 0(または適切なコード)を書く
  • /b を忘れない。exit だけ書くと cmd.exe ごと終了する
  • 終了コードは必ず明示する。exit /b(数値なし)は直前の ERRORLEVEL を伝播させる

より詳しい使い方はEXIT /B 完全解説を、サブルーチンの呼び出し構造についてはラベル・GOTO・CALL 完全ガイドをあわせて参照してください。