【bat】goto コマンド完全ガイド|ラベルジャンプ・条件分岐・ループ・:EOF・落とし穴・CALL との使い分けまで徹底解説

goto はバッチファイルの実行を指定したラベルの位置にジャンプさせるコマンドです。条件分岐・ループ・エラー処理・サブルーチン風の構造など、バッチスクリプトの骨格を作るうえで欠かせない存在です。本記事では基本構文からラベルの命名規則・:EOF の特殊用法・for ループ内の制限・CALL との使い分けまで、goto にまつわるすべてを実践例付きで解説します。

この記事でわかること

  • goto コマンドの基本構文とラベルの定義方法
  • ラベルの命名規則・制限・大文字小文字の扱い
  • goto を使った条件分岐(else-if チェーン)パターン
  • goto によるループ(カウンタ・無限ループ・break 相当)
  • :EOF 特殊ラベルで関数末尾・スクリプト末尾にジャンプ
  • for ループ内での goto の制限と回避策
  • gotoCALL サブルーチンの使い分け
スポンサーリンク

1. goto の基本構文とラベルの定義

goto は「goto ラベル名」と書くだけです。ラベルは コロン(:)で始まる行として定義します。goto を実行すると、そのラベルが書かれた行の次の行から実行が再開されます。

@echo off

echo 処理A
goto :skip

echo この行は実行されない
echo この行も実行されない

:skip
echo 処理B(:skip の次の行から再開)
pause

上の例では goto :skip によって間の2行が飛ばされ、:skip の次の行から実行が続きます。ラベル名の前のコロンは goto の引数では 省略可能ですが、付けることで「ラベルへのジャンプ」であることが明確になります。

記述 意味 備考
goto :label コロン付きでジャンプ 推奨スタイル。ラベルと区別しやすい
goto label コロンなしでジャンプ 動作は同じ。古いスクリプトに多い
goto :EOF スクリプト末尾・サブルーチン末尾へジャンプ 特殊ラベル(後述)。定義不要

2. ラベルの命名規則と注意事項

ラベルは見た目シンプルですが、いくつかの重要な仕様があります。これを知らないと予期せぬ動作を招くことがあります。

項目 仕様・注意点
定義方法 :ラベル名 で始まる行(コロン必須)
大文字小文字 区別しない。:START:start は同じラベル
有効文字数 先頭から 最大8文字 のみ有効。
:SECTION_A:SECTION_B は先頭8文字が同じため同一ラベルとみなされる(バグの元)
使える文字 アルファベット・数字・_(アンダースコア)・-(ハイフン)
スペース ラベル名にスペースは使えない(:my label はNG)
重複ラベル 同名ラベルが複数ある場合は最初に見つかったものにジャンプ
ラベル行の内容 : 以降はコメント扱い。::comment もラベルとして動作する
8文字制限によるバグに注意
ラベルの有効文字は先頭8文字のみです。
たとえば :SECTION_ALPHA:SECTION_BETA はいずれも先頭8文字が SECTION_ で一致するため、同一ラベルとみなされます
ラベル名は短く、かつ8文字以内で一意になるよう命名してください。
:: NG例:先頭8文字が同じラベルは区別されない
:SECTION_A    ← 有効部分は "SECTION_"
  echo Section A

:SECTION_B    ← 有効部分は "SECTION_" → :SECTION_A と同じ!
  echo Section B  ← ここに飛んでくるつもりでも :SECTION_A が使われる

:: OK例:先頭8文字が一意になるよう命名
:SEC_A
  echo Section A

:SEC_B
  echo Section B

3. goto を使った条件分岐パターン

goto の最も典型的な用途が if と組み合わせた条件分岐です。if 条件 goto :ラベル の形で「条件が真ならジャンプ」が実現できます。

3-1. 基本の if / goto 分岐

@echo off

set /p ANSWER=続けますか? (y/n): 

if /i "%ANSWER%"=="y" goto :yes
if /i "%ANSWER%"=="n" goto :no

echo 無効な入力です。y または n を入力してください。
goto :EOF

:yes
echo 処理を続けます
goto :EOF

:no
echo 処理を中止しました
goto :EOF

3-2. else-if チェーン(メニュー分岐)

バッチファイルには else if 構文がないため、goto を使った連続的な条件チェックで代替します。

@echo off

:menu
echo ================
echo  1. バックアップ
echo  2. 復元
echo  3. ログ確認
echo  0. 終了
echo ================
set /p CHOICE=番号を選択してください: 

if "%CHOICE%"=="1" goto :backup
if "%CHOICE%"=="2" goto :restore
if "%CHOICE%"=="3" goto :viewlog
if "%CHOICE%"=="0" goto :EOF

echo 無効な選択です。もう一度入力してください。
goto :menu

:backup
echo バックアップを実行します
:: ... バックアップ処理 ...
goto :menu

:restore
echo 復元を実行します
:: ... 復元処理 ...
goto :menu

:viewlog
echo ログを表示します
:: ... ログ表示 ...
goto :menu

条件分岐の網羅的なパターン(AND/OR・ERRORLEVEL との組み合わせ等)については バッチファイルで条件分岐する方法完全ガイド も参照してください。

4. goto によるループパターン

for ループが使えない場面や、より柔軟な制御が必要な場合に goto によるループが活用できます。ただし一般的な繰り返しには for の方が適切です(後述)。

4-1. カウンタループ

@echo off
setlocal enabledelayedexpansion

set COUNT=1
set MAX=5

:loop
if !COUNT! gtr %MAX% goto :done

echo 処理中: !COUNT! / %MAX%
:: ... 実際の処理 ...

set /a COUNT+=1
goto :loop

:done
echo %MAX% 件の処理が完了しました

4-2. 無限ループと break 相当の脱出

@echo off
setlocal enabledelayedexpansion

set RETRY=0
set MAX_RETRY=3

:retry_loop
:: 処理を試みる(ここでは ping で疎通確認を例に)
ping -n 1 192.168.1.1 >nul 2>&1
if not errorlevel 1 goto :connected

set /a RETRY+=1
echo [!RETRY!/%MAX_RETRY%] 接続失敗。リトライします...

if !RETRY! geq %MAX_RETRY% goto :give_up
timeout /t 5 /nobreak >nul
goto :retry_loop

:connected
echo 接続成功!処理を続けます
goto :EOF

:give_up
echo リトライ上限に達しました。処理を中断します
exit /b 1

4-3. goto ループ vs for ループの使い分け

場面 推奨する構文 理由
単純な数値カウント(1〜N) for /l %%I in (1,1,N) for /l の方が簡潔・高速
ファイルやフォルダをループ for %%F in (*) for が適切。goto でのファイルループは困難
リトライループ(n回まで) goto ループ リトライ上限・待機・条件脱出が柔軟に書ける
無限ループ + 条件終了 goto ループ break 相当が goto で自然に実現できる
再帰処理 CALL :label サブルーチン goto では戻り先が管理できない

5. :EOF 特殊ラベル ── 定義不要で使えるジャンプ先

:EOF(End Of File)は定義しなくても使える特殊ラベルです。goto :EOF と書くと、スクリプトの末尾(または CALL で呼び出した場合は呼び出し元)に制御が戻ります。setlocal と組み合わせてサブルーチンを作る際の「return」として使われます。

@echo off

:: メイン処理
call :greet Alice
call :greet Bob
echo 全員への挨拶完了
goto :EOF
:: ↑ここで「サブルーチンをスキップして」スクリプト末尾へ

:greet
    echo こんにちは、%~1 さん!
    goto :EOF
    :: ↑呼び出し元(call :greet の次の行)に戻る
goto :EOF と exit /b の違い
goto :EOF はラベルへのジャンプであり、setlocal のスコープを終了しません。ERRORLEVEL もそのまま引き継がれます。
exit /b は現在のバッチ(またはサブルーチン)を終了し、ERRORLEVEL を指定できます。サブルーチンの終端では exit /b 0 の方が意図が明確になる場合が多いです。
詳細は EXIT /B 完全解説 を参照してください。
@echo off

:: goto :EOF を使った場合(ERRORLEVEL は変わらない)
:sub1
    echo サブルーチン1
    goto :EOF

:: exit /b を使った場合(戻り値を明示できる)
:sub2
    echo サブルーチン2
    exit /b 0

:: exit /b 1 でエラーを呼び出し元に通知
:check_file
    if not exist "%~1" (
        echo ファイルが見つかりません: %~1
        exit /b 1
    )
    exit /b 0

6. for ループ内での goto の制限

for ループ内で goto を使うとループ全体が終了する
for ループの中で goto を実行すると、ループの残り処理を全部スキップしてジャンプします。「次のイテレーションに進む(continue)」の代わりに使うことはできません。
@echo off

:: NG例:for ループ内の goto はループ全体を抜ける
for %%I in (1 2 3 4 5) do (
    if "%%I"=="3" goto :skip_item
    echo 処理: %%I
    :: ↑ %%I=3 のときに goto で抜けると、4と5も処理されない!
)
echo ループ完了
goto :EOF

:skip_item
echo ※ 3以降がスキップされた

6-1. for ループ内でのスキップ(continue 相当)の正しい書き方

@echo off

:: OK例1: フラグ変数でスキップを制御
for %%I in (1 2 3 4 5) do (
    if "%%I"=="3" (
        echo %%I はスキップ
    ) else (
        echo 処理: %%I
    )
)
@echo off

:: OK例2: call で別ラベルに処理を委譲(continue に近い動作)
for %%I in (1 2 3 4 5) do (
    call :process %%I
)
echo ループ完了
goto :EOF

:process
    if "%~1"=="3" (
        echo %~1 はスキップ
        exit /b 0
    )
    echo 処理: %~1
    exit /b 0

7. goto の後方ジャンプ(逆スキャン)の仕組みと影響

goto がラベルを探す仕組みを理解しておくと、大きなスクリプトでのパフォーマンスや予期せぬ動作を防げます。

ジャンプ方向 仕組み 注意点
前方ジャンプ(下に進む) 現在行より後ろのラベルを順に探す 高速。通常の条件分岐・エラー処理に使う
後方ジャンプ(上に戻る) ファイル先頭からスキャンしてラベルを探す ループで多用すると遅くなる可能性がある(巨大スクリプトで顕在化)
@echo off
setlocal enabledelayedexpansion

:: 後方ジャンプ:ファイル先頭からスキャンして :loop を探す
:: → 小規模スクリプトでは問題ないが、1000行超では速度に影響することがある

set /a N=0
:loop
set /a N+=1
echo !N!
if !N! lss 5 goto :loop

echo 完了

8. goto と CALL サブルーチン ── 使い分けの基準

gotoCALL :label は共にラベルへジャンプしますが、戻り先があるかどうかが根本的に異なります。

観点 goto CALL :label
戻り先 戻らない(一方向ジャンプ) 呼び出し元の次の行に戻る
引数 渡せない call :label arg1 arg2 で渡せる
戻り値 なし exit /b N で ERRORLEVEL を設定できる
用途 条件分岐・ループ・エラー脱出 再利用可能なサブルーチン・関数
ネスト 不可(再帰できない) できる(ただし深いネストは注意)
@echo off

:: goto の用途: 条件分岐・ループ・エラー脱出
call :check_exist "C:\work\data.csv"
if errorlevel 1 goto :error

echo 正常処理
goto :EOF

:error
echo エラーが発生しました。処理を中断します。
exit /b 1

:: CALL の用途: 戻り値付きサブルーチン
:check_exist
    if not exist "%~1" (
        echo [ERROR] ファイルが見つかりません: %~1
        exit /b 1
    )
    exit /b 0

サブルーチン(CALL :label)の引数・戻り値・再帰の詳細については バッチファイルのサブルーチン完全ガイド、また GOTO・CALL・LABEL を総合的に解説した ラベル・GOTO・CALL 完全ガイド も参照してください。

9. 実践例3本

実践例1:メニュー付き対話型バッチ(繰り返し表示・入力バリデーション付き)

@echo off
setlocal enabledelayedexpansion

:main_menu
cls
echo ========================================
echo  ファイル管理ツール v1.0
echo ========================================
echo  1. ファイル一覧を表示
echo  2. バックアップを実行
echo  3. ログを表示
echo  9. ツールの設定
echo  0. 終了
echo ========================================
set CHOICE=
set /p CHOICE=選択してください [0-3,9]: 

:: 入力バリデーション
if "!CHOICE!"=="" goto :main_menu
if "!CHOICE!"=="0" goto :exit_tool
if "!CHOICE!"=="1" goto :list_files
if "!CHOICE!"=="2" goto :do_backup
if "!CHOICE!"=="3" goto :show_log
if "!CHOICE!"=="9" goto :settings

echo 無効な入力: !CHOICE!
timeout /t 1 /nobreak >nul
goto :main_menu

:list_files
    cls
    echo --- ファイル一覧 ---
    dir /b C:\work\ 2>nul || echo ファイルがありません
    echo.
    pause
    goto :main_menu

:do_backup
    echo バックアップを実行します...
    :: ... バックアップ処理 ...
    echo バックアップ完了
    pause
    goto :main_menu

:show_log
    echo --- ログ ---
    if exist C:\logs\app.log (
        type C:\logs\app.log
    ) else (
        echo ログファイルがありません
    )
    echo.
    pause
    goto :main_menu

:settings
    echo 設定画面(省略)
    pause
    goto :main_menu

:exit_tool
    echo 終了します。お疲れ様でした。
    exit /b 0

実践例2:リトライ付きファイル待機(最大N回まで待つ)

外部プロセスが生成するファイルを一定時間・一定回数まで待つパターンです。タイムアウト後はエラーとして終了します。

@echo off
setlocal enabledelayedexpansion

set WAIT_FILE=C:\work\trigger.done
set MAX_WAIT=12
set INTERVAL=5
set ELAPSED=0

echo ファイル出現を待機中: %WAIT_FILE%

:wait_loop
if exist "%WAIT_FILE%" goto :file_found

set /a ELAPSED+=1
if !ELAPSED! gtr %MAX_WAIT% goto :timeout

set /a WAITED_SEC=!ELAPSED! * %INTERVAL%
echo [!ELAPSED!/%MAX_WAIT%] 待機中... (経過: !WAITED_SEC!秒)
timeout /t %INTERVAL% /nobreak >nul
goto :wait_loop

:file_found
echo ファイルを検出しました。処理を開始します。
del "%WAIT_FILE%" >nul 2>&1
:: ... 本処理 ...
exit /b 0

:timeout
echo タイムアウト: %WAIT_FILE% が %MAX_WAIT% 回待っても出現しませんでした
exit /b 1

実践例3:エラーハンドリング付きバッチ処理(共通エラーラベル)

複数のステップがある処理で、各ステップのエラーを共通の :error ラベルで集約するパターンです。各ステップで異なるエラーメッセージも設定できます。

@echo off
setlocal enabledelayedexpansion

set ERR_MSG=

:: ステップ1: ソースファイルの確認
if not exist "C:\work\data.csv" (
    set ERR_MSG=ソースファイルが見つかりません: C:\work\data.csv
    goto :error
)

:: ステップ2: 出力ディレクトリの確認・作成
if not exist "C:\output\" (
    mkdir "C:\output\" 2>nul
    if errorlevel 1 (
        set ERR_MSG=出力ディレクトリを作成できません: C:\output\
        goto :error
    )
)

:: ステップ3: データ処理
copy "C:\work\data.csv" "C:\output\data_backup.csv" >nul 2>&1
if errorlevel 1 (
    set ERR_MSG=ファイルのコピーに失敗しました
    goto :error
)

:: 全ステップ完了
echo [OK] 全処理が正常に完了しました
exit /b 0

:: 共通エラーハンドラ
:error
echo [ERROR] !ERR_MSG!
echo 処理を中断しました。ログを確認してください。
exit /b 1

エラーハンドリングのパターン全般については エラー時に処理を中断・終了する方法完全ガイド も参照してください。

10. まとめ:goto コマンドのチートシート

やりたいこと コード
指定ラベルにジャンプ goto :ラベル名
スクリプト末尾(またはサブルーチン戻り)へ goto :EOF(定義不要の特殊ラベル)
条件が真ならジャンプ if 条件 goto :ラベル
メニュー分岐(else-if チェーン) if "%C%"=="1" goto :s1 / if "%C%"=="2" goto :s2 / ...
goto ループ(カウンタ) :loop / set /a N+=1 / if !N! leq %MAX% goto :loop
for ループ内 continue 相当 goto は使えないif/elsecall で代替
共通エラー処理へジャンプ if errorlevel 1 goto :error
ラベル命名の注意 先頭8文字が一意になるよう短く命名する

FAQ

Qgoto でジャンプした後、元の位置に戻ることはできますか?

Agoto は一方向のジャンプであり、元の位置には戻れません。処理を終えて呼び出し元に戻りたい場合は CALL :label を使ってください。CALL :label は関数(サブルーチン)として動作し、サブルーチン内で goto :EOF または exit /b を実行すると呼び出し元の次の行に戻ります。

Qラベルが見つからない場合はどうなりますか?

A存在しないラベルに goto しようとすると、ラベル名 というラベルは存在しません。 というエラーが表示されてバッチが終了します。ラベル名の誤字(大文字小文字は区別されない)や8文字制限による重複がないか確認してください。

Qgoto :EOF は必ずスクリプトの末尾に :EOF ラベルを定義する必要がありますか?

Aいいえ、:EOF定義不要の特殊ラベルです。setlocal/endlocal と組み合わせてサブルーチンを作るための組み込み機能です。バッチの最後に :EOF と書く必要はありません。

Qfor ループ内で特定の条件のときだけ次の要素に skip(continue)したいのですが、goto で実現できますか?

Afor ループ内では goto を使うとループ全体が終了してしまうため、continue 相当の動作は実現できません。スキップしたい要素を if/else で分岐するか、call :subroutine 内で exit /b を使って次のイテレーションに進む方法を使ってください。

Qgoto label と goto :label(コロン付き)の動作は違いますか?

A動作は同じです。コロンはあってもなくても goto はラベルを探します。ただし :label と書くことで「ラベルへのジャンプ」であることが視覚的に明確になるため、コロン付きを推奨します。特に goto :EOF はコロンなし(goto EOF)では動作しないため注意してください。

Q変数を使った動的な goto(goto %VAR% のようなもの)は使えますか?

Aはい、goto %VAR% は使えます。ただし VAR の値がスクリプト内のラベル名と一致している必要があります。入力値をそのまま goto に渡すと存在しないラベルへジャンプしてエラーになる可能性があるため、事前にバリデーション(if "%VAR%"=="allowed_label" goto %VAR%)を行うことが重要です。