バックアップスクリプトや設定変更スクリプトを書いていると、ひとつのミスで取り返しのつかない操作を実行してしまうことがある。
この記事では、bash組み込みコマンドの
read と select を使った対話式シェルスクリプトの作り方を解説する。確認プロンプト・番号付きメニュー選択・入力検証のパターンを、実際のスクリプトとともに紹介する。なお
expect コマンドは「スクリプトが受け取るプロンプトへ自動で答える」ツールであるのに対し、read/select は「人間の判断をスクリプトに組み込む」用途に使う。棲み分けを押さえておくと迷わない。この記事のポイント
・read -p でプロンプト付きのユーザー入力を1行で受け取れる
・select でPS3プロンプト付きの番号メニューを簡単に実装できる
・while ループと条件分岐で「正しい入力が来るまで繰り返す」UIが作れる
・入力検証を外出しした関数にすると再利用しやすい
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ対話式シェルスクリプトが必要なのか
完全自動化がシェルスクリプトの理想ではある。しかし実務では「最終確認だけは人間が行いたい」操作が必ずある。典型例を挙げると、
・本番ディレクトリの一括削除
・データベースのダンプリストア
・本番設定ファイルの上書き
・デプロイ先(本番/ステージング)の選択
こういった操作に確認UIを挟むことで、誤操作によるインシデントを防ぎながら、手順は自動化できる。
セミナーでも「スクリプトを書けるようになってから、むしろ確認コードを丁寧に書くようになった」という受講生が多い。自動化と確認は対立しない。
readコマンドの基本 ─ 標準入力から入力を受け取る
read はbash組み込みのコマンドで、標準入力から1行を読み取り、指定した変数に代入する。1. -p オプションでプロンプトを同一行に表示する
最も使うのが-p オプションだ。プロンプト文字列を改行なしで表示しつつ、同じ行でユーザー入力を待つ。#!/bin/bash # 基本的なread -p の使い方 read -p "処理を続けますか?[y/N]: " answer echo "入力値: ${answer}"
$ bash confirm_basic.sh 処理を続けますか?[y/N]: y 入力値: y
answer にユーザーが入力した文字列が入る。何も入力せずEnterを押した場合は空文字列になる。2. -s オプションでパスワードを非表示入力する
-s(silent)オプションを付けると、入力した文字がターミナルに表示されない。パスワードや機密情報の入力に使う。#!/bin/bash # パスワード入力(画面に表示しない) read -s -p "パスワード: " passwd echo "" # 改行を明示的に出す echo "パスワードを受け取りました(長さ: ${#passwd}文字)"
-s を使うと入力後の改行も表示されないため、echo "" で明示的に改行を入れる習慣をつけておくと、次の出力行がずれない。3. -t オプションでタイムアウトを設定する
-t で秒数を指定すると、入力がなかった場合に read が非ゼロで終了する。バックグラウンドジョブや自動化スクリプト内でタイムアウト付きの確認を入れたいときに使う。#!/bin/bash # 10秒以内に入力がなければスキップ if read -t 10 -p "10秒以内に確認してください [y/N]: " answer; then echo "入力: ${answer}" else echo "" echo "タイムアウト ─ デフォルト動作で続行します" answer="N" fi
4. -n オプションで入力文字数を制限する
-n 1 を付けると、1文字入力した瞬間に自動でEnterが押されたとみなして read が終了する。「y/n だけ入力させてEnterは不要」という確認UIで便利だ。#!/bin/bash # 1文字入力で即反応(Enterが不要) read -n 1 -p "続行しますか?[y/N]: " answer echo "" # 改行
selectコマンドでメニュー選択を実装する
select はbash組み込みの制御構文で、リストを自動的に番号付きメニューとして表示し、ユーザーの番号入力を受け取る。手動でメニューを echo する必要がなく、コードが簡潔になる。1. selectの基本構文とPS3プロンプト
#!/bin/bash # selectの基本構文 PS3="番号を入力してください: " select item in "バックアップ開始" "設定確認" "終了"; do case "${item}" in "バックアップ開始") echo "バックアップを開始します" break ;; "設定確認") echo "現在の設定を表示します" break ;; "終了") echo "終了します" break ;; *) echo "無効な番号です。1~3で選択してください" ;; esac done
$ bash menu.sh 1) バックアップ開始 2) 設定確認 3) 終了 番号を入力してください: 1 バックアップを開始します
PS3 変数に代入した文字列が、選択肢一覧の下に表示されるプロンプトになる。デフォルトは #? で読みにくいため、必ず PS3 をセットする習慣をつけること。select の中で item には選択された項目の文字列が、組み込み変数 REPLY には入力された番号が入る。番号が範囲外の場合は item が空文字列になるため、*) で捕捉できる。2. 実用的な管理メニューを作る
複数の管理操作をひとつのスクリプトにまとめるときに役立つ。以下はWebサーバーの簡易管理メニューの例だ。#!/bin/bash # Webサーバー管理メニュー PS3="操作番号を選択: " while true; do echo "" echo "=== Webサーバー管理メニュー ===" select action in "ステータス確認" "再起動" "設定テスト" "終了"; do case "${action}" in "ステータス確認") systemctl status httpd ;; "再起動") read -p "httpd を再起動します。よろしいですか?[y/N]: " confirm if [[ "${confirm}" =~ ^[Yy]$ ]]; then systemctl restart httpd echo "再起動しました" else echo "キャンセルしました" fi ;; "設定テスト") httpd -t ;; "終了") echo "終了します" exit 0 ;; *) echo "番号が正しくありません" ;; esac break done done
while true と select を組み合わせることで、ユーザーが「終了」を選ぶまで何度でも操作を繰り返せる。対話式シェルスクリプトの設計パターンをまとめて学びたい方は、シェルスクリプト実践講座(Linux Master Pro)でread/select以外のUI設計パターンも体系的に学べます。
入力検証パターン ─ 正しい入力が来るまでループする
一度だけread を呼ぶだけでは不十分な場合がある。ユーザーが想定外の値を入力したときに、エラーメッセージを出して再入力を求める仕組みが必要だ。1. yes/no確認プロンプト(関数化して使い回す)
#!/bin/bash # y/n確認関数(関数化して再利用) confirm() { local prompt="${1:-続行しますか?}" local answer while true; do read -p "${prompt} [y/N]: " answer case "${answer}" in [Yy]) return 0 ;; # 0=true(yes) [Nn]|"") return 1 ;; # 1=false(no/デフォルト) *) echo "y または n を入力してください" ;; esac done } # 使用例 if confirm "ログファイルを削除しますか?"; then echo "削除を実行します" else echo "キャンセルしました" fi
$ bash delete_logs.sh ログファイルを削除しますか? [y/N]: maybe y または n を入力してください ログファイルを削除しますか? [y/N]: y 削除を実行します
2. 数値の範囲チェック
保持する世代数や待機秒数など、数値の入力が必要なケースは以下のパターンが使いやすい。#!/bin/bash # 1~7の整数入力を強制する read_integer() { local prompt="${1}" local min="${2}" local max="${3}" local value while true; do read -p "${prompt} (${min}~${max}): " value # 整数かつ範囲内かチェック if [[ "${value}" =~ ^[0-9]+$ ]] \ && (( value >= min && value <= max )); then echo "${value}" return 0 fi echo "入力エラー: ${min} ~ ${max} の整数を入力してください" done } # 使用例 generations=$(read_integer "バックアップ保持世代数" 1 7) echo "保持世代数: ${generations}"
3. ファイルパス入力の存在チェック
#!/bin/bash # 実在するファイルのパスが入力されるまでループ read_existing_file() { local filepath while true; do read -p "設定ファイルのパス: " filepath if [[ -f "${filepath}" ]]; then echo "${filepath}" return 0 fi echo "エラー: ファイルが見つかりません ─ ${filepath}" done } target=$(read_existing_file) echo "処理対象: ${target}"
実践: 削除前確認付きのクリーンアップスクリプト
これまでのパターンを組み合わせた実用スクリプトを示す。30日以上更新されていないログファイルを削除する前に、対象一覧を表示してからユーザー確認を取る。#!/bin/bash set -euo pipefail LOG_DIR="${1:-/var/log/myapp}" DAYS_OLD=30 # 確認関数 confirm() { local answer while true; do read -p "${1} [y/N]: " answer case "${answer}" in [Yy]) return 0 ;; [Nn]|"") return 1 ;; *) echo "y または n を入力してください" ;; esac done } # 削除対象を検索 echo "=== ${DAYS_OLD}日以上更新なしのログファイル ===" mapfile -t targets < <(find "${LOG_DIR}" -maxdepth 1 -name "*.log" -mtime +"${DAYS_OLD}" 2>/dev/null) if [[ ${#targets[@]} -eq 0 ]]; then echo "削除対象ファイルはありません" exit 0 fi for f in "${targets[@]}"; do ls -lh "${f}" done echo "" if confirm "上記 ${#targets[@]} 件を削除しますか?"; then for f in "${targets[@]}"; do rm -v "${f}" done echo "削除完了: ${#targets[@]}件" else echo "キャンセルしました" fi
$ bash cleanup.sh /var/log/myapp === 30日以上更新なしのログファイル === -rw-r--r-- 1 root root 1.2M Apr 12 08:30 /var/log/myapp/access.log.2026040101 -rw-r--r-- 1 root root 456K Apr 5 22:15 /var/log/myapp/error.log.2026040101 上記 2 件を削除しますか? [y/N]: y removed '/var/log/myapp/access.log.2026040101' removed '/var/log/myapp/error.log.2026040101' 削除完了: 2件
set -euo pipefail を先頭に入れておくことで、予期しないエラーが起きたときにスクリプトが即時終了する。対話式スクリプトでも安全性は必須だ。トラブルシュート
readが入力を待たずにスキップされる
パイプ経由やリダイレクトでスクリプトを起動すると、標準入力がパイプ/ファイルで上書きされてread が即座に終了することがある。# NG: パイプ経由では標準入力がコマンドの出力になるため readが入力を待たない some_command | bash interactive.sh # OK: 端末から強制的に読む ─ /dev/tty を使う read -p "確認: " answer < /dev/tty
< /dev/tty をつけることで確実に端末入力を受け付けられる。selectで番号が範囲外のときに空文字列になる
select は入力が選択肢の番号に一致しない場合、変数を空文字列にしてループを続ける。*) で必ずフォールバックを書くこと。また REPLY 変数に生の入力が入っているため、デバッグ時に確認できる。# 範囲外入力のデバッグ例 select item in "A" "B" "C"; do if [[ -z "${item}" ]]; then echo "無効な入力: REPLY=${REPLY}" continue fi echo "選択: ${item}" break done
read -n 1 で日本語の最初の文字だけ取れてしまう
-n は「バイト数」ではなく「文字数」を指定するが、環境によってはマルチバイト文字で予期しない動作をすることがある。y/n などASCII 1文字の入力に限定して使うのが安全だ。本記事のまとめ
| やりたいこと | コマンド・構文 |
|---|---|
| プロンプト付きで1行入力を受け取る | read -p "プロンプト: " 変数名 |
| パスワードを非表示で入力する | read -s -p "パスワード: " 変数名 |
| タイムアウト付きで入力を待つ | read -t 秒数 -p "プロンプト: " 変数名 |
| 1文字入力で即反応する | read -n 1 -p "プロンプト: " 変数名 |
| 番号付きメニューを表示・選択させる | PS3="プロンプト: "; select 変数 in 選択肢...; do ... done |
| 正しい入力が来るまで再入力させる | while true; do read ...; [検証]; break; done |
| パイプ経由でも端末入力を受け取る | read -p "..." 変数名 < /dev/tty |
read と select を組み合わせることで、シェルスクリプトに「人間の判断」を自然な形で組み込める。確認プロンプトひとつで誤操作によるインシデントを防げることを考えると、実装コストは小さく効果は大きい。対話式UIの設計と、エラー処理・trapを組み合わせた堅牢なスクリプト設計をさらに深めたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場での設計パターンを体系的に学べます。
スクリプトに「確認UI」を加えると、自動化の事故が激減します
read/selectで人間の判断を挟む設計は、シェルスクリプトの安全運用において欠かせないパターンです。しかし「どこで確認を入れるか」「エラー処理と組み合わせるか」といった設計判断は、断片的な知識では身につきにくい部分でもあります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:#!/bin/shと#!/bin/bashの違いとPOSIX互換スクリプトの書き方|dash環境で壊れないための移植性設計
- 前のページへ:bats-coreでシェルスクリプトをテストする方法|ユニットテストの書き方とCI自動実行
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます