read コマンド入門 - シェルスクリプトで入力を受け取る

read コマンド入門 - シェルスクリプトで入力を受け取る

「スクリプトの途中で、ユーザーに名前やパスワードを入力してもらいたい」——そんなときに使うのが read コマンドです。この記事では、ライニー先輩とリナの会話を通じて、read でキーボード入力やファイルを受け取る方法を一緒に学んでいきましょう。

この記事でわかること

  • read コマンドが何をするコマンドなのかがわかる
  • プロンプト表示(-p)・パスワード非表示(-s)・タイムアウト(-t)の使い方を習得できる
  • while read でファイルを1行ずつ処理する定番パターンが書ける
  • 初心者がハマりやすい「パイプの落とし穴」「-r を付ける理由」を理解できる

1. read コマンドって何?

結論: read は標準入力から1行を読み取って変数に入れる組み込みコマンドで、スクリプトをユーザーと対話させるための入り口になる。

リナ: ライニー先輩、シェルスクリプトを書いてたら「ここでユーザーに入力してもらいたい」って場面が出てきたんです。こういうのってどうやるんですか?
ライニー先輩: それなら read コマンドの出番だよ。readキーボードから打たれた1行を読み取って、変数に入れてくれるコマンドなんだ。

read コマンドとは

read は、標準入力(キーボードなど)から1行を読み取り、変数に格納するシェルの組み込みコマンドです。ユーザーに何かを入力してもらい、その内容をスクリプトの中で使いたいときに利用します。

リナ: 「組み込みコマンド」って、ls とか cat とは違うんですか?
ライニー先輩: いい質問だね。lscat/bin/ls のように実行ファイルが存在するコマンドなんだけど、readシェル自身が持っている機能なんだ。だから which read で探しても出てこないことが多いよ。type read で確認してみると read is a shell builtin って表示されるんだ。

2. いちばん基本の使い方

結論: read 変数名 と書くと入力待ちになり、Enter を押すまでの1行がその変数に入る。

リナ: さっそく使ってみたいです。いちばんシンプルな形を教えてください。
ライニー先輩: じゃあ、こう打ってみて。Enter を押すとカーソルが止まって入力待ちになるよ。
read name
リナ: 打ったら、画面が止まりました。ここで リナ って入力して Enter を押すと...?
ライニー先輩: その「リナ」という文字列が name という変数に入るんだ。中身を確認してみよう。
read name
リナ
echo "こんにちは、$name さん"
こんにちは、リナ さん

変数の中身を見るには $ を付ける

read name で入れた値は、$name のように頭に $ を付けて取り出します。echo "$name" のようにダブルクォートで囲むのが安全な書き方です(値に空白が含まれても正しく扱えます)。

3. プロンプトを表示する(-p)

結論: read -p "メッセージ" 変数名 と書くと、入力欄の前に案内文を表示できる。

リナ: でも、ただカーソルが止まるだけだと、何を入力すればいいのか分からないですよね?
ライニー先輩: そのとおり。だから普通は -p オプションで**入力を促すメッセージ(プロンプト)**を一緒に表示するんだ。
read -p "お名前を入力してください: " name
echo "ようこそ、$name さん"
お名前を入力してください: リナ
ようこそ、リナ さん
リナ: なるほど! これなら何を入力すればいいか一目で分かりますね。
ライニー先輩: そう。-p の後ろに書いた文字列がそのまま表示されるよ。: のように最後に空白を入れておくと、入力位置が見やすくなるからおすすめだよ。

4. 複数の値を一度に受け取る

結論: read に変数名を複数並べると、空白で区切られた入力をそれぞれの変数に振り分けられる。

リナ: 名前と年齢みたいに、いくつかまとめて入力してもらうことはできますか?
ライニー先輩: できるよ。read の後ろに変数名を空白で区切って並べるんだ。入力も空白で区切ると、それぞれに振り分けられるよ。
read -p "姓 名 を入力: " sei mei
echo "姓: $sei / 名: $mei"
姓 名 を入力: 山田 リナ
姓: 山田 / 名: リナ

変数より入力が多いと、最後の変数にまとめて入る

read a b のように変数が2つなのに 1 2 3 4 と入力すると、a には 1b には残り全部(2 3 4)が入ります。逆に入力が少ないと、余った変数は空のままになります。

5. パスワードを隠して入力する(-s)

結論: read -s は入力した文字を画面に表示しないので、パスワードなど見られたくない入力に使う。

リナ: パスワードを入力してもらうとき、画面に丸見えだと困りますよね...?
ライニー先輩: そこで -s(silent)オプションだよ。入力した文字が画面に表示されなくなるんだ。パスワードや秘密の値を受け取るときに使うよ。
read -s -p "パスワード: " pass
echo
echo "入力された文字数: ${#pass}"
パスワード:
入力された文字数: 8
リナ: 入力しても画面に何も出ませんでした。でも、ちゃんと受け取れてるんですね。
ライニー先輩: そう。-s は文字を表示しないだけで、中身はちゃんと変数に入っているんだ。-s を使うと Enter を押しても改行が表示されないから、直後に echo で改行を1つ入れておくと見た目がきれいになるよ。${#pass}変数の文字数を表す書き方だよ。

6. 入力を時間制限つきにする(-t)

結論: read -t 秒数 を使うと、指定した秒数以内に入力がないと read が自動で終了する。

リナ: もし誰も入力しなかったら、スクリプトはずっと止まったままになっちゃいますよね?
ライニー先輩: それを防ぐのが -t(timeout)だよ。指定した秒数だけ待って、入力がなければ次に進むんだ。
if read -t 5 -p "5秒以内に入力してね: " answer; then
    echo "入力されました: $answer"
else
    echo
    echo "時間切れです"
fi

read の成功・失敗を if で判定できる

read は、正常に1行読めれば「成功」、タイムアウトや入力の終わり(後述)になると「失敗」を返します。これを if read ...; then の形で受け取ると、入力があったかどうかで処理を分けられます。

7. ファイルを1行ずつ読む(while read)

結論: while read line; do ... done < ファイル で、ファイルを1行ずつ変数に取り込んで処理できる。

リナ: キーボードだけじゃなくて、ファイルの中身を1行ずつ処理したいときもありますよね?
ライニー先輩: それには while read の組み合わせがいちばんだよ。これはシェルスクリプトの超定番パターンだから、形ごと覚えておくと一生使えるよ。
while read line; do
    echo "読んだ行: $line"
done < list.txt
リナ: < list.txt の部分は何ですか?
ライニー先輩: これはリダイレクトといって、「list.txt の中身を read に流し込んでね」という意味だよ。read は1行読むたびに line に入れて、行がなくなるまでループを繰り返すんだ。ファイルの最後まで読むと read が「失敗」を返すから、自然にループが終わるんだよ。

より安全な型: while IFS= read -r line

実務では while IFS= read -r line; do ... done < file という形をよく見ます。IFS= は行頭・行末の空白が削られるのを防ぎ、-r はバックスラッシュ(\)をそのまま読み込むための指定です。理由は次の章で説明します。

8. 初心者がハマる2つの落とし穴

結論: パイプで渡した while read は別プロセスで動くため変数が消える点と、-r なしだとバックスラッシュが消える点に注意する。

リナ: 先輩、while read を使ったら、ループの中で数えた数がループの後で 0 に戻ってて...バグですか?
ライニー先輩: それは「あるある」だね。バグじゃなくてパイプの仕組みが原因なんだ。順番に見ていこう。

落とし穴1: パイプの中の変数は外に残らない

count=0
cat list.txt | while read line; do
    count=$((count + 1))
done
echo "$count"   # ← 0 になってしまう
リナ: なんで 0 なんですか? ちゃんと数えてるはずなのに...
ライニー先輩: cat ... | while ... のようにパイプでつなぐと、while の部分が**別のプロセス(サブシェル)**で動くんだ。その中で count を増やしても、それは「分身」の中だけの話。元の count には反映されないんだよ。

解決策: パイプではなくリダイレクトを使う

count=0
while read line; do
    count=$((count + 1))
done < list.txt
echo "$count"   # ← 正しく数えられる

< list.txt のリダイレクトなら、while は同じプロセスの中で動くので、count の変更がちゃんと残ります。

落とし穴2: -r を付けないとバックスラッシュが消える

リナ: もう1つの -r って、なんで付けるんですか?
ライニー先輩: -r を付けないと、readバックスラッシュ(\)を特別扱いして消してしまうんだ。例えばファイルのパス C:\Users を読むと C:Users になっちゃう。だから「入力された文字をそのまま読みたい」ときは -r を付けるのが鉄則だよ。
# -r なし: \ が消えてしまう
echo 'C:\Users\rina' | read path; echo "$path"
# → C:Usersrina

# -r あり: そのまま読める
echo 'C:\Users\rina' | read -r path; echo "$path"
# → C:\Users\rina

迷ったら -r を付ける

特別な理由がない限り、read には -r を付けるのが安全です。シェルスクリプトのチェックツール(ShellCheck など)も、-r なしの read を警告します。

9. ミニ課題で復習しよう

リナ: いろいろ学んだので、自分でも書いてみたいです!
ライニー先輩: いいね。じゃあ簡単な課題を出すよ。名前を尋ねて、あいさつを返すスクリプトを書いてみよう。
課題: あいさつスクリプトを書いてみよう(クリックで解答例)

お題: 「お名前は?」と尋ね、入力された名前を使って「こんにちは、〇〇さん!」と表示するスクリプトを書いてください。

解答例:

#!/bin/bash
read -p "お名前は? " name
echo "こんにちは、${name}さん!"

-p でプロンプトを出し、$name で受け取った値を表示しています。${name} のように {} で囲むと、変数名の区切りがはっきりして安全です。

リナ: できました! read -p でプロンプトを出して、$name で表示するんですね。
ライニー先輩: その調子だよ。read は短いコマンドだけど、-p-s-twhile read の4つを押さえれば、たいていの「入力を受け取る場面」に対応できる。あとは実際のスクリプトでどんどん使って慣れていこう。

まとめ

read コマンドの要点

  • read 変数名 で標準入力の1行を変数に格納する
  • -p "メッセージ" で入力を促すプロンプトを表示
  • -s で入力を画面に表示しない(パスワード向け)
  • -t 秒数 で入力待ちにタイムアウトを設定
  • while read line; do ... done < file でファイルを1行ずつ処理
  • パイプではなくリダイレクト(< file)を使い、-r を付けるのが安全な型

read をマスターすると、スクリプトが「自動で流れるだけのもの」から「ユーザーと対話できるもの」に進化します。次はシェルスクリプト入門で、受け取った入力を条件分岐やループと組み合わせる方法を学んでみましょう。