- 技術 💻
シェルスクリプトreadコマンドの使い方|y/n確認とファイル1行ずつ
シェルスクリプトの read コマンドとは、標準入力から1行を読み取り、変数に入れるコマンドです。 キーボードからの入力を受け取る場面でも、ファイルを1行ずつ処理する場面でも、同じ read を使います。違いは「標準入力がどこにつながっているか」だけです。
この記事では、キーボード入力(y/n 確認・パスワード・時間制限)とファイル読み込み(while read・IFS・CSV)を1ページにまとめました。 -p・-s・-t・-n・-r・-a のオプション早見表、そのまま貼って使える y/n 確認の関数、そしてつまずきやすい4つの動き(最終行が読まれない、パイプ経由で変数が消える、ループ内の ssh で1行目で止まる、前後の空白が消える)を、bash 5.2.21 で再現して直し方まで確かめています。
readコマンドの早見表
よく使う形
| やりたいこと | 書き方 |
|---|---|
| メッセージを出して入力を受ける | read -r -p "名前: " name |
| パスワードを画面に出さずに受ける | read -r -s -p "パスワード: " pass; echo |
| 時間制限つきで受ける | read -r -t 10 -p "入力: " ans |
| 1文字だけ受ける(Enter 不要) | read -r -n 1 -p "[y/n]: " key; echo |
| 空白区切りを複数の変数に分ける | read -r last first age |
| 空白区切りを配列に入れる | read -r -a items |
| ファイルを1行ずつ読む | while IFS= read -r line; do ...; done < file |
| CSV を列に分けて読む | while IFS=, read -r id name qty; do ...; done < file |
| 文字列を変数に分ける | read -r a b <<< "hello world" |
オプション
| オプション | はたらき | 補足 |
|---|---|---|
-p 文字列 |
入力の前にメッセージを表示する | 端末からの入力のときだけ表示される |
-s |
入力した文字を画面に表示しない | 改行も表示されないので直後に echo を置く |
-t 秒数 |
指定した秒数で入力を打ち切る | 時間切れの終了コードは 128 より大きい値(確認では 142) |
-n 文字数 |
指定した文字数を読んだら戻る | 区切り(改行)が先に来ればそこで戻る |
-N 文字数 |
ちょうど指定した文字数を読む | 改行も1文字として数える |
-r |
バックスラッシュを特別扱いしない | 基本的に毎回付ける |
-a 配列名 |
単語に分けて配列に入れる | 添字は 0 から |
-d 文字 |
改行の代わりに、指定した文字までを1件として読む | -d '' で NUL 区切り |
-e |
行の編集機能(矢印キーなど)を使う | -i 文字列 で初期値を入れられる |
-u 番号 |
指定した番号のファイル記述子から読む | read -r line <&3 と同じ用途 |
終了コード
| 終了コード | 意味 |
|---|---|
| 0 | 1行を読み取れた |
| 1 | 入力の終わり(EOF)に達した |
| 128 より大きい値 | -t の時間切れ |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「シェルスクリプトreadコマンドの使い方|y/n確認とファイル1行ずつ」
この記事の確認環境
| 項目 | 内容 |
|---|---|
| シェル | GNU bash 5.2.21 |
| 比較用の sh | dash 0.5.12(Ubuntu 系で /bin/sh が指すシェル) |
| ssh | OpenSSH 9.6p1(同じマシン内に確認用のサーバーを立てて接続) |
| キーボード入力の確認方法 | 擬似端末を使い、キー入力をプログラムから送って画面の表示を記録 |
| 仕様の参照元 | bash 5.2 に付属するヘルプ(help read) |
キーボード入力の例は、人が手で打つ代わりにプログラムから同じキーを送って記録しています。出力例の中で、プロンプトの右にある文字が「入力した内容」です。
help read の冒頭には、次のように書かれています。
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
キーボード入力を受け取る基本
メッセージを出して入力を受ける
#!/usr/bin/env bash
read -r -p "お名前を入力してください: " name
echo "こんにちは、${name} さん"
$ bash b1.sh
お名前を入力してください: taro
こんにちは、taro さん
-p でメッセージを表示し、入力された1行が変数 name に入ります。変数名には $ を付けません。
パスワードを画面に出さずに受ける
#!/usr/bin/env bash
read -r -s -p "パスワード: " pass
echo
echo "入力された文字数: ${#pass}"
$ bash b2.sh
パスワード:
入力された文字数: 9
secret123 と入力しましたが、画面には表示されていません。-s は Enter の改行も表示しないため、直後に echo を1つ置いて行を改めます。
時間制限を付ける
#!/usr/bin/env bash
if read -r -t 2 -p "2秒以内に入力してください: " ans; then
echo "入力: $ans"
else
rc=$?
echo
echo "時間切れです (終了コード $rc)。既定値で進みます"
fi
何も入力せずに待った場合と、ok と入力した場合です。
$ bash b3.sh
2秒以内に入力してください:
時間切れです (終了コード 142)。既定値で進みます
$ bash b3.sh
2秒以内に入力してください: ok
入力: ok
read を if の条件に置くと、読めたかどうかで分岐できます。 夜間に自動で動かすスクリプトなど、人が答えない状況でも先へ進めたいときに役立ちます。
1文字だけ受ける
#!/usr/bin/env bash
read -r -n 1 -p "続けますか? [y/n]: " key
echo
echo "押されたキー: $key"
$ bash b4.sh
続けますか? [y/n]: y
押されたキー: y
y を押した時点で、Enter を待たずに次へ進みます。-s と組み合わせると「何かキーを押すと続行」も書けます。
read -r -s -n 1 -p "何かキーを押すと続行します..."
echo
echo "続行しました"
何かキーを押すと続行します...
続行しました
複数の変数・配列・変数名なし
#!/usr/bin/env bash
read -r -p "姓 名 年齢: " last first age
echo "姓=$last 名=$first 年齢=$age"
read -r -p "好きな果物を空白区切りで: " -a fruits
echo "件数=${#fruits[@]} 1番目=${fruits[0]} 最後=${fruits[-1]}"
read -r -p "何か入力: "
echo "REPLY=$REPLY"
$ bash b5.sh
姓 名 年齢: yamada taro 30 extra
姓=yamada 名=taro 年齢=30 extra
好きな果物を空白区切りで: apple banana cherry
件数=3 1番目=apple 最後=cherry
何か入力: hello
REPLY=hello
| 書き方 | 動き |
|---|---|
| 変数を複数並べる | 空白で区切って順に入る。余った分は最後の変数にまとめて入る(30 extra) |
-a 配列名 |
単語の数だけ配列の要素になる |
| 変数名を書かない | 読んだ行が REPLY という変数に入る |
初期値を入れておく
-e と -i を組み合わせると、入力欄にあらかじめ値を入れておけます。そのまま Enter を押せば初期値が採用され、書き換えることもできます。
#!/usr/bin/env bash
read -r -e -i "/var/log/app.log" -p "ファイル名: " path
echo "path=$path"
$ bash b6.sh
ファイル名: /var/log/app.log
path=/var/log/app.log
y/n確認のコピペ用関数
確認メッセージを出し、既定値つき・再入力ループつきで y/n を受ける関数です。スクリプトの上のほうに貼って使えます。
#!/usr/bin/env bash
# confirm "質問" [既定値 y|n]
# y → 戻り値 0、n → 戻り値 1。Enter だけなら既定値。その他は聞き直す
confirm() {
local prompt=$1 default=${2:-} hint answer
case "$default" in
[yY]) hint="[Y/n]" ;;
[nN]) hint="[y/N]" ;;
*) hint="[y/n]" ;;
esac
while true; do
read -r -p "$prompt $hint: " answer || return 1
case "${answer:-$default}" in
[yY]|[yY][eE][sS]) return 0 ;;
[nN]|[nN][oO]) return 1 ;;
*) echo "y か n で答えてください。" ;;
esac
done
}
if confirm "ファイルを削除しますか?" n; then
echo "削除しました"
else
echo "中止しました"
fi
Enter だけを押した場合(既定値 n が採用される):
$ bash confirm.sh
ファイルを削除しますか? [y/N]:
中止しました
maybe と入力したあと、Yes と入力し直した場合:
$ bash confirm.sh
ファイルを削除しますか? [y/N]: maybe
y か n で答えてください。
ファイルを削除しますか? [y/N]: Yes
削除しました
| 工夫 | ねらい |
|---|---|
[Y/n]・[y/N] の表示 |
大文字のほうが既定値、という慣習に合わせて読み手に伝える |
${answer:-$default} |
Enter だけ(空文字)のときに既定値を使う |
[yY][eE][sS] |
y、Y、yes、Yes、YES のどれでも受ける |
while true と *) |
想定外の入力は聞き直す |
read ... || return 1 |
入力が終わっている(EOF)ときは「いいえ」として戻り、ループを抜ける |
| 戻り値 0/1 | if confirm ... や confirm ... || exit 1 と自然につなげられる |
呼び出し方の例
confirm "続行しますか?" y && echo "続行します"
confirm "本当に実行しますか?" || { echo "中止しました"; exit 1; }
echo "実行しました"
1問目は Enter のみ、2問目は Enter のあと n と入力した結果です。既定値を指定していない2問目は、Enter だけでは先に進まず聞き直しています。
続行しますか? [Y/n]:
続行します
本当に実行しますか? [y/n]:
y か n で答えてください。
本当に実行しますか? [y/n]: n
中止しました
取り消せない操作の確認は既定値を n にするか、既定値なしにすると、うっかり Enter を押しても安全側に倒れます。
自動実行のときの動き
標準入力が端末でない場合(パイプやリダイレクト)の動きも確かめました。
$ echo "y" | bash confirm.sh
削除しました
$ bash confirm.sh < /dev/null
中止しました
echo "y" | で答えを流し込めば自動で進められ、入力が空なら「いいえ」として終わります。-p のメッセージは端末からの入力のときだけ表示されるため、上の例ではプロンプトが出ていません。
入力を確かめて聞き直す・メニューから選ぶ
数字が入力されるまで聞き直す
入力された値は、使う前に形を確かめます。case で「数字だけか」を、if で「範囲に入っているか」を見て、合わなければ聞き直す形です。
#!/usr/bin/env bash
# 1〜10 の数字が入力されるまで聞き直す
while true; do
read -r -p "個数を入力してください (1-10): " qty
case "$qty" in
''|*[!0-9]*) echo "数字で入力してください。"; continue ;;
esac
if [ "$qty" -ge 1 ] && [ "$qty" -le 10 ]; then
break
fi
echo "1 から 10 の範囲で入力してください。"
done
echo "個数: $qty"
abc、25、7 の順に入力した結果です。
$ bash num.sh
個数を入力してください (1-10): abc
数字で入力してください。
個数を入力してください (1-10): 25
1 から 10 の範囲で入力してください。
個数を入力してください (1-10): 7
個数: 7
番号でメニューを選ぶ
read と case を while で囲むと、選び直しのできるメニューになります。
#!/usr/bin/env bash
echo "1) 一覧を表示"
echo "2) 追加する"
echo "q) 終了"
while true; do
read -r -p "番号を選んでください: " choice
case "$choice" in
1) echo "一覧を表示します" ;;
2) echo "追加します" ;;
q|Q) echo "終了します"; break ;;
*) echo "1、2、q のどれかを入力してください。" ;;
esac
done
$ bash menu.sh
1) 一覧を表示
2) 追加する
q) 終了
番号を選んでください: 1
一覧を表示します
番号を選んでください: x
1、2、q のどれかを入力してください。
番号を選んでください: q
終了します
| 形 | 使いどころ |
|---|---|
while true + read + case + break |
正しい入力が来るまで聞き直す、メニューをくり返し表示する |
continue |
条件に合わないときに、ループの先頭へ戻って聞き直す |
read ... || break |
入力が終わっている(EOF)ときにループを抜ける。自動実行でも回り続けずに済む |
ファイルを1行ずつ読む
基本の形
#!/usr/bin/env bash
n=0
while IFS= read -r line; do
n=$((n + 1))
echo "$n: $line"
done < fruits.txt
echo "合計 $n 行"
fruits.txt の中身は apple・banana・cherry の3行です。
$ bash f1.sh
1: apple
2: banana
3: cherry
合計 3 行
while IFS= read -r line; do ... done < ファイル が、ファイルを1行ずつ読む基本形です。 それぞれの部品の役割は次のとおりです。
| 部品 | 役割 |
|---|---|
while ... ; do ... done |
read が成功(終了コード 0)する間くり返す |
IFS= |
行の前後の空白やタブを残す(read の間だけ有効) |
read -r |
バックスラッシュをそのまま読む |
line |
読んだ1行が入る変数 |
< fruits.txt |
ループ全体の標準入力をファイルにする |
ファイルの終わりに達すると read が終了コード 1 を返し、ループが終わります。
CSVや区切り文字のあるデータを読む
IFS に区切り文字を指定すると、1行を複数の変数に分けられます。
#!/usr/bin/env bash
{
read -r header # 1行目(見出し)を読み飛ばす
while IFS=, read -r id name qty; do
echo "id=$id name=$name qty=${qty:-0}"
done
} < items.csv
echo "IFS は元のまま: $(printf '%q' "$IFS")"
items.csv の中身:
id,name,qty
1,ノート,10
2,ペン,25
3,付箋,
$ bash csv.sh
id=1 name=ノート qty=10
id=2 name=ペン qty=25
id=3 name=付箋 qty=0
IFS は元のまま: $' \t\n'
| ポイント | 説明 |
|---|---|
IFS=, read ... |
read の前に書いた IFS は、その read にだけ効く。スクリプト全体の IFS は変わらない |
{ read -r header; while ...; } < file |
最初の read で1行目を読み、残りをループで処理する |
${qty:-0} |
空の列に既定値を入れる |
列の数と変数の数が合わないときの動きも確かめました。
$ bash -c 'IFS=, read -r a b <<< "1,2,3,4"; echo "a=$a b=$b"'
a=1 b=2,3,4
$ bash -c 'IFS=, read -r -a cols <<< "a,b,,d"; echo "${#cols[@]}: ${cols[*]}"'
4: a b d
$ bash -c 'IFS=: read -r user _ uid _ <<< "taro:x:1000:1000:Taro:/home/taro:/bin/bash"; echo "user=$user uid=$uid"'
user=taro uid=1000
変数が足りないと、余った列は最後の変数にまとめて入ります。使わない列は _ という名前の変数に受けると、必要な列だけ取り出せます。列の数が決まっていないときは -a で配列に入れます。
なお、この方法は「値の中にカンマや改行を含まない」単純な CSV 向けです。引用符で囲まれた値を含む CSV は、専用の機能を持つツールや言語で扱うのが向いています。
forで読む書き方との違い
ファイルの中身を for に渡す書き方は、行ではなく空白で区切った単語ごとに回ります。hw.txt(1行目が hello world、2行目が bye)で比べました。
for line in $(cat hw.txt); do echo "[$line]"; done
[hello]
[world]
[bye]
2行のファイルなのに3回まわっています。行単位で処理したいときは while IFS= read -r line を選びます。
空白を含むファイル名を1件ずつ受ける
find の結果を読むときは、-print0 と read -d '' を組み合わせると、空白を含むファイル名も1件として扱えます。
while IFS= read -r -d '' f; do
echo "[$f]"
done < <(find d -type f -print0 | sort -z)
[d/a b.txt]
[d/c.txt]
全行を配列に入れてから処理する
行数が多くないファイルなら、mapfile(別名 readarray)で配列にまとめて読む方法もあります。あとから「2行目だけ」「行数だけ」を取り出せます。
mapfile -t lines < fruits.txt
echo "行数: ${#lines[@]}"
echo "2行目: ${lines[1]}"
for l in "${lines[@]}"; do echo "- $l"; done
行数: 3
2行目: banana
- apple
- banana
- cherry
-t は各行の末尾の改行を取り除くオプションです。mapfile は bash の機能で、最後に改行がない nonl.txt でも3行すべてが配列に入ることを確かめました。
2つのファイルを同時に1行ずつ読む
ファイルごとに別の番号(3番と4番)を割り当てると、2つのファイルを並べて読めます。
while IFS= read -r name <&3 && IFS= read -r age <&4; do
echo "$name さんは $age 歳"
done 3< names.txt 4< ages.txt
taro さんは 30 歳
hanako さんは 25 歳
ほかの読み込み方
| やりたいこと | 書き方 | 確認結果 |
|---|---|---|
| 全行を配列に入れる | mapfile -t lines < fruits.txt |
3 行、${lines[1]} は banana |
| 空白を含むファイル名を安全に受ける | while IFS= read -r -d '' f; do ...; done < <(find d -type f -print0) |
a b.txt を1件として読めた |
| ちょうど3文字読む | read -r -N 3 v <<< "abcdef" |
v=abc |
| 文字列から読む | read -r a b <<< "hello world" |
a=hello b=world |
つまずきやすい4つの動きと直し方
1. 最終行が読まれない
ファイルの最後に改行がないと、最終行が処理されません。nonl.txt は cherry のあとに改行がないファイルです。
while IFS= read -r line; do
echo "読んだ行: $line"
done < nonl.txt
読んだ行: apple
読んだ行: banana
cherry が出てきません。理由を確かめるため、改行のない入力を read に渡してみます。
$ printf 'abc' | { read -r v; echo "rc=$? v=$v"; }
rc=1 v=abc
変数には abc が入っていますが、終了コードは 1(入力の終わり) です。while は終了コードを見るので、ループの本体が実行されずに終わります。直し方は、条件に「変数に中身があれば続ける」を足すことです。
while IFS= read -r line || [ -n "$line" ]; do
echo "読んだ行: $line"
done < nonl.txt
読んだ行: apple
読んだ行: banana
読んだ行: cherry
| 直す前 | 直した後 |
|---|---|
while IFS= read -r line; do |
while IFS= read -r line || [ -n "$line" ]; do |
エディタの設定によって最後の改行が付かなかったファイルや、printf で書き出したファイルで起こります。人が作ったファイルを読むスクリプトでは、最初からこの形にしておくと安心です。
2. パイプ経由で変数が消える
ループの中で数えた値が、ループのあとで 0 に戻っている、という動きです。
count=0
cat fruits.txt | while IFS= read -r line; do
count=$((count + 1))
done
echo "パイプ経由: count=$count"
count=0
while IFS= read -r line; do
count=$((count + 1))
done < fruits.txt
echo "リダイレクト: count=$count"
count=0
while IFS= read -r line; do
count=$((count + 1))
done < <(grep -v '^#' fruits.txt)
echo "プロセス置換: count=$count"
パイプ経由: count=0
リダイレクト: count=3
プロセス置換: count=3
bash では、パイプの右側は別のプロセス(サブシェル)で動きます。 その中で変えた変数は、ループが終わると一緒に消えます。同じ理由で、次の書き方も値が残りません。
$ bash -c 'echo "hello world" | read -r a b; echo "a=$a b=$b"'
a= b=
$ bash -c 'read -r a b <<< "hello world"; echo "a=$a b=$b"'
a=hello b=world
| 入力のもと | 直し方 |
|---|---|
| ファイル | done < ファイル とリダイレクトで渡す |
| コマンドの出力 | done < <(コマンド) とプロセス置換で渡す |
| 短い文字列 | read ... <<< "文字列" とヒアストリングで渡す |
| パイプのまま書きたい | スクリプト冒頭で shopt -s lastpipe を有効にする |
shopt -s lastpipe を付けたスクリプトでは、パイプ経由でも count=3、a=hello b=world になることを確かめました。lastpipe は、パイプの最後のコマンドを今のシェルで動かす bash の設定で、スクリプトとして実行したときに効きます。
3. ループ内のsshで1行目で止まる
ホスト名の一覧を読みながら ssh でコマンドを実行すると、1件目だけで終わることがあります。同じマシン内に確認用のサーバーを立て、hosts.txt(web01・web02・web03 の3行)で再現しました。接続先やポートは -F で指定した設定ファイルに書いてあります。
while IFS= read -r host; do
echo "接続: $host"
ssh -F ssh/cfg "$host" 'echo " 応答OK"'
done < hosts.txt
接続: web01
応答OK
エラーも出ず、終了コードも 0 のまま、1件目で終わりました。ssh は標準入力を読み込んで接続先へ送るコマンドです。 ループの標準入力は hosts.txt につながっているので、1回目の ssh が残りの行(web02・web03)をすべて読み取ってしまい、次の read には読むものが残っていません。
ssh を使わなくても、標準入力を読むコマンドなら同じ動きになります。
while IFS= read -r host; do
echo "処理中: $host"
cat > /dev/null # ssh と同じく標準入力を読むコマンド
done < hosts.txt
処理中: web01
直し方は3つあり、どれも3件すべて処理できることを確かめました。
# 直し方A: ssh に -n を付ける(標準入力を読まない)
ssh -n -F ssh/cfg "$host" 'echo " 応答OK"'
# 直し方B: ssh の標準入力を /dev/null にする
ssh -F ssh/cfg "$host" < /dev/null 'echo " 応答OK"'
# 直し方C: ループ側が別の番号(3番)から読む
while IFS= read -r host <&3; do
ssh -F ssh/cfg "$host" 'echo " 応答OK"'
done 3< hosts.txt
接続: web01
応答OK
接続: web02
応答OK
接続: web03
応答OK
(直し方 A・B・C のどれも、ssh で同じ表示になりました。)
| 直し方 | 向いている場面 |
|---|---|
ssh -n |
ssh だけが原因のとき。いちばん短い |
< /dev/null |
ssh 以外のコマンド(標準入力を読むツール全般)にも使える |
<&3 と 3< |
ループの中でキーボード入力も受けたいとき |
ループの中でキーボード入力も受ける
直し方 C の形にすると、ファイルは3番から、キーボードは通常の標準入力から読めるので、1件ずつ確認しながら処理するスクリプトが書けます。
#!/usr/bin/env bash
while IFS= read -r f <&3; do
read -r -p "$f を処理しますか? [y/n]: " ans
case "$ans" in
[yY]*) echo " → $f を処理しました" ;;
*) echo " → スキップ" ;;
esac
done 3< fruits.txt
$ bash loop_confirm.sh
apple を処理しますか? [y/n]: y
→ apple を処理しました
banana を処理しますか? [y/n]: n
→ スキップ
cherry を処理しますか? [y/n]: y
→ cherry を処理しました
4. 前後の空白が消える
IFS= と -r を付けない read は、行の前後の空白を取り除き、バックスラッシュを解釈します。前後に空白のある行と、タブで始まり Windows 形式のパスを含む行で比べました(^I はタブを表します)。
echo "--- read line ---"
while read line; do echo "[$line]"; done < sp.txt
echo "--- read -r line ---"
while read -r line; do echo "[$line]"; done < sp.txt
echo "--- IFS= read -r line ---"
while IFS= read -r line; do echo "[$line]"; done < sp.txt
--- read line ---
[indented line]
[C:tempnew]
--- read -r line ---
[indented line]
[C:\temp\new]
--- IFS= read -r line ---
[ indented line ]
[^IC:\temp\new]
| 書き方 | 前後の空白・タブ | バックスラッシュ |
|---|---|---|
read line |
消える | 消える(C:tempnew) |
read -r line |
消える | 残る |
IFS= read -r line |
残る | 残る |
行をそのまま読みたいときは IFS= read -r と覚えます。 インデントに意味がある設定ファイルや、パスを含むデータを扱うときに差が出ます。反対に、前後の空白を取り除きたい場合は read -r line をあえて使う、という選び方もできます。
4つのまとめ
| 動き | 原因 | 直し方 |
|---|---|---|
| 最終行が読まれない | 最後に改行がなく、read が終了コード 1 を返す | 条件に || [ -n "$line" ] を足す |
| ループ後に変数が元に戻る | パイプの右側が別プロセスで動く | done < file または done < <(コマンド) |
| ssh で1行目で止まる | ssh が残りの行を読み取る | ssh -n、< /dev/null、<&3 |
| 前後の空白やバックスラッシュが消える | IFS による切り詰めとエスケープの解釈 | IFS= read -r |
4つすべてに対応した形は次のとおりです。
while IFS= read -r line <&3 || [ -n "$line" ]; do
# ここに処理を書く(ssh やキーボード入力もそのまま使える)
printf '%s\n' "$line"
done 3< input.txt
書き終えたら確かめるチェックリスト
| 確認すること | 見るポイント |
|---|---|
read に -r を付けたか |
バックスラッシュを含む入力をそのまま受ける |
行をそのまま読む箇所は IFS= を付けたか |
前後の空白とタブを残す |
| ループの入力はパイプではなくリダイレクトか | ループの中で変えた変数をあとで使える |
条件に || [ -n "$line" ] を足したか |
最後に改行がないファイルでも最終行を処理する |
| ループの中に標準入力を読むコマンドがないか | ssh には -n、そのほかは < /dev/null |
-s の直後に echo を置いたか |
次の表示が同じ行に続かないようにする |
| 取り消せない操作の確認は既定値が n か | Enter だけで進まないようにする |
-s・-t・-n・-a を使うなら bash で動かしているか |
1行目を #!/usr/bin/env bash にする |
bashとshでの違い
#!/bin/sh のスクリプトでは、使えるオプションが限られます。dash 0.5.12 で確かめた結果です。
| オプション | bash 5.2 | dash 0.5.12(sh) |
|---|---|---|
-r |
使える | 使える |
-p |
使える | 使える |
-s |
使える | read: Illegal option -s |
-t |
使える | read: Illegal option -t |
-n |
使える | read: Illegal option -n |
-a |
使える | read: Illegal option -a |
変数名なし(REPLY) |
使える | 未実行 |
-p は今回の dash では動きましたが、sh の種類によって対応が分かれるオプションです。sh でも動かすスクリプトでは、メッセージを printf で出してから read する形が広く使えます。
#!/bin/sh
printf '続けますか? [y/n]: '
read -r ans
case "$ans" in
[yY]*) echo "続けます" ;;
*) echo "中止します" ;;
esac
$ echo y | sh posix.sh
続けますか? [y/n]: 続けます
-s・-t・-n・-a を使うときは、1行目を #!/usr/bin/env bash にして bash で実行します。
よくある質問
Q. read に -r は毎回必要ですか?
A. 基本的に毎回付けるのがおすすめです。-r がないと、C:\temp\new が C:tempnew になるように、バックスラッシュが取り除かれます。バックスラッシュで行を次の行につなげたい、という特別な目的がある場合だけ外します。
Q. 読み込むファイル名を引数で受けるにはどう書きますか?
A. done < "$1" のように、リダイレクト先を変数にします。空白を含むファイル名でも動くよう、二重引用符で囲みます。引数がないときは標準入力から読む、という形にしたい場合は done < "${1:-/dev/stdin}" と書けます(ファイル名を渡した場合とパイプで流した場合の両方で動作を確認済み)。
Q. Enter だけ押されたかどうかを判定するには?
A. 変数が空かどうかを調べます。[ -z "$ans" ] が真なら Enter だけです。既定値を使いたいなら ${ans:-y} のように書くと、空のときだけ y になります。
Q. -t で時間切れになったことはどう見分けますか?
A. read の終了コードを見ます。今回の確認では、時間切れは 142、入力の終わり(EOF)は 1 でした。help read には「時間切れの場合は 128 より大きい」と書かれているので、[ $rc -gt 128 ] で判定できます。
まとめ
- read は標準入力から1行を読んで変数に入れるコマンドで、キーボード入力にもファイル読み込みにも使います。
- 対話用のオプションは
-p(メッセージ)、-s(非表示)、-t(時間制限)、-n(文字数)、分割用は-a(配列)です。-rは毎回付けます。 - y/n 確認は、既定値と再入力ループを備えた
confirm関数を貼っておくと、if confirm "..." n; thenの1行で書けます。 - ファイルを1行ずつ読む基本形は
while IFS= read -r line; do ... done < fileです。CSV はIFS=, read -r a b cで列に分けます。 - 最終行が読まれないときは
|| [ -n "$line" ]、変数が消えるときはパイプをやめて< fileや< <(コマンド)に書き換えます。 - ループ内の ssh は
-nか< /dev/nullを付けると、残りの行を読み取らず、全件を処理できます。 -s・-t・-n・-aは dash では使えませんでした。 使うときは bash で実行します。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「シェルスクリプトreadコマンドの使い方|y/n確認とファイル1行ずつ」
株式会社RJCでは、シェルスクリプトのreadコマンドや入力処理の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。