goto はバッチファイルの実行を指定したラベルの位置にジャンプさせるコマンドです。条件分岐・ループ・エラー処理・サブルーチン風の構造など、バッチスクリプトの骨格を作るうえで欠かせない存在です。本記事では基本構文からラベルの命名規則・:EOF の特殊用法・for ループ内の制限・CALL との使い分けまで、goto にまつわるすべてを実践例付きで解説します。
gotoコマンドの基本構文とラベルの定義方法- ラベルの命名規則・制限・大文字小文字の扱い
gotoを使った条件分岐(else-if チェーン)パターンgotoによるループ(カウンタ・無限ループ・break 相当):EOF特殊ラベルで関数末尾・スクリプト末尾にジャンプforループ内でのgotoの制限と回避策gotoとCALLサブルーチンの使い分け
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文字のみです。
たとえば
: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 はラベルへのジャンプであり、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 を実行すると、ループの残り処理を全部スキップしてジャンプします。「次のイテレーションに進む(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 サブルーチン ── 使い分けの基準
goto と CALL :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/else か call で代替 |
| 共通エラー処理へジャンプ | 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%)を行うことが重要です。