【bat】バッチファイルで選択メニューと入力値分岐を実装する完全ガイド|set /p・choice・バリデーション・多段メニュー・リトライ・タイムアウトまで

【bat】バッチファイルで選択メニューと入力値分岐を実装する完全ガイド|set /p・choice・バリデーション・多段メニュー・リトライ・タイムアウトまで bat

バッチファイルで「メニューを表示して番号を入力させたい」「フォルダ名を入力させて処理を実行したい」「不正入力のときに再入力を促したい」——こうしたインタラクティブな操作はset /pchoice を使って実現できます。

本記事では、単純な番号選択からバリデーション・リトライループ・多段メニュー・タイムアウト付き自動選択まで、実務で使えるパターンを体系的に解説します。

この記事でわかること

  • set /p でユーザーのキー入力をバッチ変数に受け取る基本
  • choice コマンドで番号/文字キーによるメニューを実装する方法
  • 空入力・範囲外・数値以外を弾くバリデーション処理の実装
  • 不正入力時に再入力を促すリトライループの作り方
  • メインメニュー→サブメニューの多段メニュー構成
  • Enterのみでデフォルト値を採用する入力パターン
  • choice /t でタイムアウト付き自動選択を実装する方法
  • 引数渡しと対話型入力の切り替えパターン
スポンサーリンク

入力方式の比較

方式 入力形式 バリデーション タイムアウト 用途
set /p 自由文字列 手動で実装 パス入力・名前入力・任意文字列
choice 1文字(キー押下) 自動(指定キーのみ) ✅ /t オプション 番号選択・Y/N確認
引数(%1〜%9) 起動時に指定 手動で実装 自動化・スクリプト化
方式の使い分け
「1〜3の番号を選ばせる」なら choice がシンプルで確実です(無効入力を自動排除)。「ファイル名やパスを入力させる」なら set /p が必要です。スクリプトとして他から呼び出す場合は引数を使い、引数がない場合に対話入力にフォールバックするパターンが実用的です。

方法1:set /p でテキスト入力を受け取る(基本)

set /p 変数名=プロンプト文字列 でユーザーの入力を待ち、Enterで確定した文字列を変数に格納します。

set /p の基本構文と番号メニュー実装
@echo off
setlocal

echo ================================
echo  メインメニュー
echo ================================
echo  1. バックアップを実行
echo  2. ログを表示
echo  3. 終了
echo ================================

set /p CHOICE=番号を入力してください (1-3): 

if "%CHOICE%"=="1" goto :BACKUP
if "%CHOICE%"=="2" goto :SHOWLOG
if "%CHOICE%"=="3" goto :END

echo 無効な入力です: %CHOICE%
goto :END

:BACKUP
echo バックアップを開始します...
goto :END

:SHOWLOG
echo ログを表示します...
goto :END

:END
endlocal
set /p の落とし穴:空入力と引用符
Enterだけを押すと %CHOICE% は空文字になります。if "%CHOICE%"=="1" の比較はif ""=="1" となり問題ありませんが、if %CHOICE%==1(クォートなし)の場合はif ==1 となりシンタックスエラーになります。必ず両辺をダブルクォートで囲む習慣をつけてください。
ファイルパスを入力させて存在確認してから処理する
@echo off
setlocal

set /p TARGET=処理対象のフォルダパスを入力してください: 

REM 空入力チェック
if "%TARGET%"=="" (
    echo パスが入力されていません
    exit /b 1
)

REM パスの存在確認
if not exist "%TARGET%" (
    echo 指定されたパスが存在しません: %TARGET%
    exit /b 1
)

echo 対象: %TARGET%
echo 処理を開始します...
dir "%TARGET%" /b
endlocal

方法2:choice コマンドで番号/文字キーメニューを実装する

choice は指定したキー以外を自動で無視するため、無効入力対応を自分で書く必要がありません。押したキーに対応する番号が %ERRORLEVEL% にセットされます(1始まり)。

errorlevel は大きい値から順に判定する
errorlevel N は「N以上」を意味します。そのため 大きい値から小さい値の順に判定しなければ正しく分岐できません。if errorlevel 3 goto Cif errorlevel 2 goto Bif errorlevel 1 goto Aの順番が正しい書き方です。
choice /c で1〜3の番号メニューを実装する(推奨パターン)
@echo off
setlocal

echo ================================
echo  操作を選択してください
echo ================================
echo  [1] バックアップ実行
echo  [2] ログ表示
echo  [3] 終了
echo ================================

choice /c 123 /n /m "番号を選択 (1-3): "

REM errorlevel は大きい値から順に判定する
if errorlevel 3 goto :END
if errorlevel 2 goto :SHOWLOG
if errorlevel 1 goto :BACKUP

:BACKUP
echo バックアップを開始します...
goto :FINISH

:SHOWLOG
echo ログを表示します...
goto :FINISH

:END
echo 終了します

:FINISH
endlocal
choice で ERRORLEVEL を変数に保存してから判定する(可読性向上版)
@echo off
setlocal

echo [B]ackup / [L]og / [Q]uit
choice /c BLQ /n /m "選択してください (B/L/Q): "
set MENU=%ERRORLEVEL%

REM 変数に保存すると後から参照しやすい
if "%MENU%"=="3" goto :QUIT
if "%MENU%"=="2" goto :LOG
if "%MENU%"=="1" goto :BACKUP

:BACKUP
echo [B] バックアップ処理
goto :END
:LOG
echo [L] ログ表示
goto :END
:QUIT
echo [Q] 終了
:END
endlocal
choice の主要オプション

オプション 意味
/c キー一覧 有効キーを指定(大文字小文字無視) /c 123 /c YN
/n キー一覧をプロンプトに表示しない
/m メッセージ プロンプトメッセージを指定 /m “選択: “
/t 秒数 タイムアウト秒数 /t 10
/d キー タイムアウト時のデフォルトキー /d N

方法3:入力バリデーションを実装する

set /p で受け取った値が正しい形式かどうかを検証する処理です。空文字・数値範囲・パス存在・数値チェックの各パターンを示します。

数値範囲バリデーション: 1〜5の範囲外は弾く
@echo off
setlocal

set /p NUM=数値を入力してください (1-5): 

REM 空入力チェック
if "%NUM%"=="" (
    echo 入力が空です
    exit /b 1
)

REM 数値かどうかのチェック(非数値を含む場合は set /a でエラーになる)
set /a NUM_TEST=%NUM% 2>nul
if %NUM_TEST% EQU 0 if not "%NUM%"=="0" (
    echo 数値を入力してください: %NUM%
    exit /b 1
)

REM 範囲チェック
if %NUM% LSS 1 ( echo 1以上の値を入力してください & exit /b 1 )
if %NUM% GTR 5 ( echo 5以下の値を入力してください & exit /b 1 )

echo 入力値: %NUM% を処理します
endlocal
文字列長チェック・禁止文字チェック
@echo off
setlocal

set /p NAME=名前を入力してください (英数字のみ、1〜20文字): 

REM 空入力チェック
if "%NAME%"=="" ( echo 入力が空です & exit /b 1 )

REM 禁止文字チェック(スペース・バックスラッシュ・スラッシュを禁止)
echo %NAME% | findstr /r "[ \\/]" >nul
if %ERRORLEVEL% equ 0 (
    echo 使用できない文字が含まれています
    exit /b 1
)

echo 名前: %NAME% で処理を続行します
endlocal

方法4:不正入力時にリトライを促すループを実装する

入力が不正なときにエラーを表示してもう一度入力を促すリトライループは、実際のツールで頻繁に使われるパターンです。リトライ上限を設けて無限ループを防ぐ実装も紹介します。

menu_retry.bat: 1〜3 以外の入力を弾いて再入力させるリトライループ
@echo off
setlocal

set MAX_RETRY=3
set RETRY=0

:INPUT_LOOP
set /a RETRY+=1
if %RETRY% gtr %MAX_RETRY% (
    echo 入力エラーが %MAX_RETRY% 回続いたため終了します
    exit /b 1
)

echo.
echo  [1] バックアップ
echo  [2] ログ表示
echo  [3] 終了
set /p CHOICE=番号を入力してください (1-3): 

if "%CHOICE%"=="1" goto :BACKUP
if "%CHOICE%"=="2" goto :LOG
if "%CHOICE%"=="3" goto :END

echo [エラー] 無効な入力です。1〜3を入力してください (試行 %RETRY%/%MAX_RETRY%)
goto :INPUT_LOOP

:BACKUP
echo バックアップを実行します
goto :FINISH
:LOG
echo ログを表示します
goto :FINISH
:END
echo 終了します
:FINISH
endlocal
数値入力のリトライループ(数値以外・範囲外を再入力させる)
@echo off
setlocal enabledelayedexpansion

:ASK_NUM
set /p NUM=削除するログの保存日数を入力してください (1-365): 

if "!NUM!"=="" ( echo 入力が空です。再入力してください & goto :ASK_NUM )

REM 数値チェック
set /a NUM_CHECK=!NUM! 2>nul
if !NUM_CHECK! equ 0 if not "!NUM!"=="0" (
    echo 数値を入力してください & goto :ASK_NUM
)

REM 範囲チェック
if !NUM! lss 1 ( echo 1以上の値を入力してください & goto :ASK_NUM )
if !NUM! gtr 365 ( echo 365以下の値を入力してください & goto :ASK_NUM )

echo %NUM%日以上前のログを削除します
forfiles /p "C:\logs" /s /m *.log /d -%NUM% /c "cmd /c del @file" 2>nul
echo 完了
endlocal

方法5:多段メニュー(メインメニュー→サブメニュー)

大きなバッチツールでは、メインメニューから機能を選んでサブメニューに遷移する多段構成が必要になります。gotocall を組み合わせて実装します。

multilevel_menu.bat: メインメニュー → サブメニューの多段構成
@echo off
setlocal enabledelayedexpansion

:MAIN_MENU
cls
echo ============================================================
echo   メインメニュー
echo ============================================================
echo   [1] ファイル管理
echo   [2] ログ管理
echo   [3] 設定
echo   [Q] 終了
echo ============================================================

choice /c 123Q /n /m "選択してください: "
set MAIN=%ERRORLEVEL%

if "%MAIN%"=="4" goto :EXIT
if "%MAIN%"=="3" call :SETTINGS_MENU & goto :MAIN_MENU
if "%MAIN%"=="2" call :LOG_MENU    & goto :MAIN_MENU
if "%MAIN%"=="1" call :FILE_MENU   & goto :MAIN_MENU
goto :MAIN_MENU

:FILE_MENU
echo.
echo  -- ファイル管理 --
echo  [1] コピー  [2] 移動  [3] 削除  [B] 戻る
choice /c 123B /n /m "選択: "
if errorlevel 4 goto :eof
if errorlevel 3 ( echo ファイルを削除します & goto :FILE_MENU )
if errorlevel 2 ( echo ファイルを移動します & goto :FILE_MENU )
if errorlevel 1 ( echo ファイルをコピーします & goto :FILE_MENU )
goto :eof

:LOG_MENU
echo.
echo  -- ログ管理 --
echo  [1] ログ表示  [2] ログ削除  [B] 戻る
choice /c 12B /n /m "選択: "
if errorlevel 3 goto :eof
if errorlevel 2 ( echo ログを削除します & goto :LOG_MENU )
if errorlevel 1 ( echo ログを表示します & goto :LOG_MENU )
goto :eof

:SETTINGS_MENU
echo.
echo  -- 設定 --
set /p LOGDIR=ログ保存フォルダを入力してください: 
echo ログフォルダを %LOGDIR% に設定しました
goto :eof

:EXIT
echo 終了します
endlocal
call でサブメニューを呼び出すメリット
call :LABEL はサブルーチン呼び出しで、goto :eof で元の呼び出し元に戻ってきます。goto :LABEL と違いスタックが積まれるため、サブメニューから必ずメインメニューに戻る構造を作れます。ただしネストを深くしすぎると call のスタック上限(64回)に達するため、深い多段メニューは設計を工夫してください。

方法6:デフォルト値付き入力(Enterのみで採用)

「何も入力しないときはデフォルト値を使う」パターンです。設定ファイルやパス入力で頻繁に使います。

default_input.bat: Enter のみでデフォルト値を採用するパターン
@echo off
setlocal

REM デフォルト値を設定
set "DEFAULT_DIR=C:\Users\%USERNAME%\Downloads"
set "DEFAULT_DAYS=30"

echo デフォルト: %DEFAULT_DIR%
set /p TARGET_DIR=対象フォルダ (Enterでデフォルト): 

REM 空入力ならデフォルト値を使用
if "%TARGET_DIR%"=="" set "TARGET_DIR=%DEFAULT_DIR%"

echo デフォルト: %DEFAULT_DAYS% 日
set /p DAYS=保存日数 (Enterでデフォルト): 
if "%DAYS%"=="" set "DAYS=%DEFAULT_DAYS%"

echo.
echo 設定内容:
echo   対象フォルダ: %TARGET_DIR%
echo   保存日数    : %DAYS% 日
echo.

choice /c YN /n /m "この設定で実行しますか? (Y/N): "
if errorlevel 2 ( echo キャンセルしました & exit /b 0 )

echo 処理を開始します...
endlocal

方法7:タイムアウト付き自動選択(choice /t /d)

choice /t 秒数 /d キー を使うと、指定した秒数内に入力がない場合にデフォルトのキーが自動選択されます。「10秒後に自動でYesを選ぶ」「確認プロンプトをスキップする」などの用途に使えます。

choice /t /d: タイムアウト付き確認プロンプト
@echo off
setlocal

echo 30秒後に自動でバックアップを開始します
choice /c YN /t 30 /d Y /m "今すぐ開始しますか? (Y=はい/N=キャンセル) [30秒でY自動選択]: "

if errorlevel 2 (
    echo キャンセルしました
    exit /b 0
)

echo バックアップを開始します...
endlocal
タイムアウトカウントダウンを表示しながら自動選択する
@echo off
setlocal

REM タイムアウト秒数をカウントダウン表示
set TIMEOUT=10
:COUNTDOWN
echo  %TIMEOUT% 秒後に自動で [N] が選択されます...
choice /c YN /t 1 /d N /n /m "続行しますか? (Y/N): " >nul 2>&1
REM タイムアウトなら N(2)が返るが、1秒ごとにカウントダウン
if errorlevel 2 (
    set /a TIMEOUT-=1
    if %TIMEOUT% gtr 0 goto :COUNTDOWN
    echo 自動的にキャンセルされました
    exit /b 0
)
echo 続行します...
endlocal
/t と /d の組み合わせ例

  • choice /c YN /t 10 /d Y: 10秒後に自動で Y(続行)
  • choice /c YN /t 10 /d N: 10秒後に自動で N(キャンセル)
  • choice /c 123 /t 5 /d 3: 5秒後に自動で 3(終了)を選択

バッチの一時停止・待機処理については処理を一時停止する方法完全ガイドも参照してください。

方法8:引数渡しと対話入力の切り替えパターン

引数が渡された場合はそれを使い、なければ対話入力にフォールバックするパターンは自動化と手動実行の両方に対応した実用的な設計です。

引数あり→そのまま実行、引数なし→対話入力でフォールバックする
@echo off
setlocal

REM 引数1: 対象フォルダ  引数2: 保存日数
REM 使い方: batch.bat "C:\work" 30
REM         batch.bat         (引数なし→対話入力)

set "TARGET=%~1"
set "DAYS=%~2"

REM 引数がない場合は対話入力
if "%TARGET%"=="" (
    set /p TARGET=対象フォルダを入力してください: 
)
if "%DAYS%"=="" (
    set /p DAYS=保存日数を入力してください (デフォルト=30): 
    if "!DAYS!"=="" set DAYS=30
)

if not exist "%TARGET%" (
    echo フォルダが存在しません: %TARGET%
    exit /b 1
)

echo 対象: %TARGET%  保存日数: %DAYS%
endlocal
引数とのハイブリッド設計のメリット
引数があればスクリプトや別バッチから自動実行でき、引数がなければユーザーが手動でターミナルから実行するときに対話入力ができます。引数の扱いについてはユーザー入力を受け取る方法完全ガイドも参照してください。

実践パターン:メニュー付き管理ツールの構成例

admin_tool.bat: バリデーション・リトライ・ログを組み込んだ実践的な管理メニュー
@echo off
setlocal enabledelayedexpansion

set "LOGFILE=C:\logs\admin_tool.log"
if not exist "C:\logs" mkdir "C:\logs"

:MAIN
cls
echo ============================================================
echo   管理ツール v1.0   %DATE% %TIME%
echo ============================================================
echo   [1] バックアップ実行
echo   [2] ログ表示
echo   [3] 古いファイル削除
echo   [Q] 終了
echo ============================================================

choice /c 123Q /n /m "選択してください: "
set SEL=%ERRORLEVEL%

if "%SEL%"=="4" goto :EXIT
if "%SEL%"=="3" call :DO_DELETE & goto :MAIN
if "%SEL%"=="2" call :DO_LOG    & goto :MAIN
if "%SEL%"=="1" call :DO_BACKUP & goto :MAIN
goto :MAIN

:DO_BACKUP
echo [%DATE% %TIME%] バックアップ開始 >> "%LOGFILE%"
set /p SRC=バックアップ元フォルダ: 
if "!SRC!"=="" ( echo 入力が空です & goto :eof )
if not exist "!SRC!" ( echo フォルダが存在しません: !SRC! & goto :eof )
echo バックアップを実行しています...
echo [%DATE% %TIME%] バックアップ完了: !SRC! >> "%LOGFILE%"
echo 完了しました
pause
goto :eof

:DO_LOG
if exist "%LOGFILE%" ( type "%LOGFILE%" ) else ( echo ログがありません )
pause
goto :eof

:DO_DELETE
:ASK_DAYS
set /p DAYS=何日以上前のファイルを削除しますか (1-365): 
if "!DAYS!"=="" ( echo 入力が空です & goto :ASK_DAYS )
set /a D=!DAYS! 2>nul
if !D! lss 1 ( echo 1以上を入力してください & goto :ASK_DAYS )
if !D! gtr 365 ( echo 365以下を入力してください & goto :ASK_DAYS )
choice /c YN /m "!DAYS!日以上前のファイルを削除します。よいですか?"
if errorlevel 2 ( echo キャンセルしました & goto :eof )
echo [%DATE% %TIME%] !DAYS!日以上前のファイルを削除 >> "%LOGFILE%"
echo 削除しました
pause
goto :eof

:EXIT
echo 終了します
endlocal
管理ツール設計のポイント

  • cls でメニュー表示前にクリアしてすっきり見せる
  • 各操作をサブルーチン(:DO_xxx)に分けて再利用しやすくする
  • ログファイルに操作日時を記録してトラブル時の調査を容易にする
  • 削除などの不可逆操作の前には choice で必ず確認を取る
  • 入力バリデーションは各サブルーチン内でリトライループとして実装する

まとめ

  • set /p: 任意文字列を受け取る。空入力は ""=="" でチェック、クォートで必ず囲む
  • choice: 指定キーのみ受け付けるメニュー。errorlevel は大きい値から順に判定する
  • バリデーション: 空チェック → 数値チェック(set /a) → 範囲チェックの3段階が基本
  • リトライループ: MAX_RETRY を設けて無限ループを防ぐ。:INPUT_LOOP + goto で実装
  • 多段メニュー: call :LABEL でサブメニューを呼び出し、goto :eof で戻る
  • デフォルト値: 空入力時に if ""=="" でデフォルト変数にセット
  • タイムアウト: choice /t N /d キー でN秒後に自動選択
  • 引数フォールバック: 引数あり→自動実行、引数なし→対話入力でハイブリッド設計

関連記事: ユーザー入力を受け取る方法完全ガイド / y/n確認による処理分岐を実現する方法 / 条件分岐する方法完全ガイド

よくある質問(FAQ)

Qset /p で入力した値が if 文で比較できません。
A最も多い原因は比較式のクォートの欠如です。if %CHOICE%==1 ではなく if "%CHOICE%"=="1" と両辺をダブルクォートで囲んでください。空入力の場合 if %CHOICE%==1if ==1 となりシンタックスエラーになります。また setlocal enabledelayedexpansion 内では !CHOICE! を使わないとループ内で更新された値が反映されません。
Qchoice コマンドの errorlevel 判定がずれています。
Aerrorlevel N は「N以上」を意味するため、必ず大きい値から小さい値の順に if 文を書いてください。たとえば choice /c 123 の場合はif errorlevel 3if errorlevel 2if errorlevel 1 の順です。変数に保存(set MENU=%ERRORLEVEL%)して if "%MENU%"=="1" と比較する方法なら順序を気にせず書けます。
Qset /p の入力に日本語を使うと文字化けします。
Aコマンドプロンプトのコードページが Shift-JIS(932)でも UTF-8(65001)でもset /p の入力は受け取れますが、バッチファイル自体の保存エンコードとコードページが一致していないとプロンプト文字列が化けます。日本語を含むバッチは Shift-JIS(CP932)で保存するのが最も安全です。エンコード問題の詳細は日本語が文字化けする原因と解決策も参照してください。
Q入力値をバッチ終了後も保持したいです。
Asetlocal を使っていると、endlocal で変数がリセットされます。呼び出し元に値を返すには endlocal & set 変数名=%ローカル変数% のパターンを使います。または設定ファイル(テキストファイル)に値を書き出して次回起動時に読み込む方法もあります(恒久的に保持したい場合)。
Qメニューをタスクスケジューラで自動実行したいのですが set /p が止まります。
Aタスクスケジューラから実行されたバッチには標準入力がないため、set /p は入力待ちで永久にブロックします。タスクスケジューラから呼び出すバッチにはset /ppause を含めないでください。かわりに引数(%1 %2)で値を渡すか、設定ファイルから読み込む設計にしてください。タスクスケジューラとの連携についてはschtasksコマンドで完全制御する完全ガイドも参照してください。