バッチファイルで「メニューを表示して番号を入力させたい」「フォルダ名を入力させて処理を実行したい」「不正入力のときに再入力を促したい」——こうしたインタラクティブな操作はset /p と choice を使って実現できます。
本記事では、単純な番号選択からバリデーション・リトライループ・多段メニュー・タイムアウト付き自動選択まで、実務で使えるパターンを体系的に解説します。
- 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で確定した文字列を変数に格納します。
@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
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 N は「N以上」を意味します。そのため 大きい値から小さい値の順に判定しなければ正しく分岐できません。if errorlevel 3 goto C → if errorlevel 2 goto B → if errorlevel 1 goto Aの順番が正しい書き方です。@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
@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
| オプション | 意味 | 例 |
|---|---|---|
| /c キー一覧 | 有効キーを指定(大文字小文字無視) | /c 123 /c YN |
| /n | キー一覧をプロンプトに表示しない | — |
| /m メッセージ | プロンプトメッセージを指定 | /m “選択: “ |
| /t 秒数 | タイムアウト秒数 | /t 10 |
| /d キー | タイムアウト時のデフォルトキー | /d N |
方法3:入力バリデーションを実装する
set /p で受け取った値が正しい形式かどうかを検証する処理です。空文字・数値範囲・パス存在・数値チェックの各パターンを示します。
@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:不正入力時にリトライを促すループを実装する
入力が不正なときにエラーを表示してもう一度入力を促すリトライループは、実際のツールで頻繁に使われるパターンです。リトライ上限を設けて無限ループを防ぐ実装も紹介します。
@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:多段メニュー(メインメニュー→サブメニュー)
大きなバッチツールでは、メインメニューから機能を選んでサブメニューに遷移する多段構成が必要になります。goto と call を組み合わせて実装します。
@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 :LABEL はサブルーチン呼び出しで、goto :eof で元の呼び出し元に戻ってきます。goto :LABEL と違いスタックが積まれるため、サブメニューから必ずメインメニューに戻る構造を作れます。ただしネストを深くしすぎると call のスタック上限(64回)に達するため、深い多段メニューは設計を工夫してください。方法6:デフォルト値付き入力(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を選ぶ」「確認プロンプトをスキップする」などの用途に使えます。
@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
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
引数があればスクリプトや別バッチから自動実行でき、引数がなければユーザーが手動でターミナルから実行するときに対話入力ができます。引数の扱いについてはユーザー入力を受け取る方法完全ガイドも参照してください。
実践パターン:メニュー付き管理ツールの構成例
@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)
if %CHOICE%==1 ではなく if "%CHOICE%"=="1" と両辺をダブルクォートで囲んでください。空入力の場合 if %CHOICE%==1 は if ==1 となりシンタックスエラーになります。また setlocal enabledelayedexpansion 内では !CHOICE! を使わないとループ内で更新された値が反映されません。errorlevel N は「N以上」を意味するため、必ず大きい値から小さい値の順に if 文を書いてください。たとえば choice /c 123 の場合はif errorlevel 3 → if errorlevel 2 → if errorlevel 1 の順です。変数に保存(set MENU=%ERRORLEVEL%)して if "%MENU%"=="1" と比較する方法なら順序を気にせず書けます。set /p の入力は受け取れますが、バッチファイル自体の保存エンコードとコードページが一致していないとプロンプト文字列が化けます。日本語を含むバッチは Shift-JIS(CP932)で保存するのが最も安全です。エンコード問題の詳細は日本語が文字化けする原因と解決策も参照してください。setlocal を使っていると、endlocal で変数がリセットされます。呼び出し元に値を返すには endlocal & set 変数名=%ローカル変数% のパターンを使います。または設定ファイル(テキストファイル)に値を書き出して次回起動時に読み込む方法もあります(恒久的に保持したい場合)。set /p は入力待ちで永久にブロックします。タスクスケジューラから呼び出すバッチにはset /p や pause を含めないでください。かわりに引数(%1 %2)で値を渡すか、設定ファイルから読み込む設計にしてください。タスクスケジューラとの連携についてはschtasksコマンドで完全制御する完全ガイドも参照してください。

