入力を読み取る(read コマンド)

read コマンドで入力を読み取る

read コマンドを利用すると標準入力やファイルから入力から1行を読み取ることができます。read コマンドには読み取った結果を代入する変数を指定することができますが、変数を指定しない場合は読み取った行は REPLY 変数に格納されます。

code
read
echo "REPLY=${REPLY}"
read
echo "REPLY=${REPLY}"
terminal
> apple orange banana⏎
REPLY=apple orange banana
> one two three⏎
REPLY=one two three

read コマンドに変数を指定した場合、読み取った行は IFS 変数に設定されている区切り文字で単語に分割されて変数に設定されます。

code
printf 'IFS=%q\n' "${IFS}"
read a b c
echo "a='${a}'"
echo "b='${b}'"
echo "c='${c}'"
terminal
IFS=$' \t\n'
> one two three⏎
a='one'
b='two'
c='three'

余った単語とその間にある区切り文字は、最後に指定した変数に設定されます。

code
read a b c
echo "a='${a}'"
echo "b='${b}'"
echo "c='${c}'"
terminal
> one two three four five⏎
a='one'
b='two'
c='three four five'

IFS 変数の値を変更することで、単語に分割する区切り文字を変更することができます。

code
IFS=',' read a b c
echo "a='${a}'"
echo "b='${b}'"
echo "c='${c}'"
terminal
> apple,orange,banana⏎
a='apple'
b='orange'
c='banana'

read コマンドで複数行の入力を読み取る

while ループで read コマンドをループすることで複数行の入力を読み取ることができます。端末上で Ctrl と d キーを同時に押すことで EOF が送信されループは終了します。

code
while read; do
  echo "${REPLY}"
done
terminal
> apple⏎
apple
> orange⏎
orange
> banana⏎
banana
> CTRL-D⏎

ファイルの内容を read コマンドで読み取る

ファイルを標準入力につなぐことで read コマンドでファイルの内容を読み取ることができます。ファイルの最後まで読み取ると while ループは終了します。

code
# 読み取るファイルを作成する
cat << 'EOF' > './sample.txt'
リンゴ,100
バナナ,120
ミカン,90
EOF

# 標準入力にファイルをつないで read コマンドで読み取る
while IFS=',' read name price; do
  echo "${name}は${price}円です"
done <'./sample.txt'
stdout
リンゴは100円です
バナナは120円です
ミカンは90円です

バックスラッシュがエスケープ文字として扱われる点に注意が必要です。無効にするには r オプションを指定します。

code
# 読み取るファイルを作成する
# 末尾にバックスラッシュがある場合、行継続とみなされる
cat << 'EOF' > './sample.txt'
リンゴ,100\
バナナ\,120
ミカン,90
EOF

echo '# r オプションなし'
while IFS=',' read name price; do
  echo "${name}は${price}円です"
done <'./sample.txt'

echo '# r オプションあり'
while IFS=',' read -r name price; do
  echo "${name}は${price}円です"
done <'./sample.txt' 
stdout
# r オプションなし
リンゴは100バナナ,120円です
ミカンは90円です
# r オプションあり
リンゴは100\円です
バナナ\は120円です
ミカンは90円です

コマンドの出力を read コマンドで読み取る

標準入力にコマンドの標準出力をつなぐことで、コマンドの出力を read コマンドで読み取ることができます。

code
intials=
while read -r word _; do
  intials="${intials}${word:0:1}"
done < <(sed --version)

echo "${intials^}"
stdout
SPCLTTWPTSGGE

パイプでつないだ場合はサブシェルで起動されることに注意が必要です。以下のサンプルのように外で宣言された変数を変更することができません。

code
intials=

# パイプでつないだ場合はサブシェルになるので外で宣言された変数を変更できない
sed --version | while read -r word _; do
  intials="${intials}${word:0:1}"
done

echo "${intials^}"
stdout

read コマンドのオプション

read コマンドに指定することができるオプションの概要は以下です。

オプション説明
-a aname単語を配列変数 aname に読み込んだ単語をインデックス順に代入します。
-d delim改行ではなく、delim の最初の文字を行の終了を表す文字として使用します。
-e標準入力を端末から読み込む場合、readline を使用した編集可能な入力を使います。カーソルの移動や文字の削除、入力の補完などの機能を端末での入力時に利用することができます。
-i text行を取得するのに readline が使われるとき、入力を開始する前に編集バッファに text が置かれます。
-n nchars入力行全体が読み込まれるのを待たずに nchars で指定した文字数の文字を読み込むか区切り文字が現れた時点で読み込み結果が戻ります。
-N nchars入力行全体が読み込まれるのを待たずに nchars で指定した文字数の文字を読み込んだ時点で戻ります。区切り文字を特別扱いしません。
-p prompt標準入力を端末から読み込む場合に、入力を読み込もうとする前に標準エラー出力に prompt を表示します。 末尾に改行は付きません。
-rバックスラッシュはエスケープ文字として作用しません。
-s端末に入力が行われても、入力された文字をエコーしません。
-t timeout入力行全体が timeout 秒以内で読み込まれない場合に失敗の状態を返します。timeout は小数を含めることができます。timeout 秒を超えた場合の終了ステータスは 128 より大きな値になります。
-u fd入力を fd で指定されたファイルディスクリプターから読み込みます。

オプションを指定した動作の確認のため、いくつかサンプルコードを記載します。

read -a 配列変数名 配列に読み込む

code
read -r -a array
declare -p array
terminal
> apple banana orange⏎
declare -a array=([0]="apple" [1]="banana" [2]="orange")

read -n 文字数 指定した文字数か区切り文字が現れるまで読み込む

-n オプションを使うと、「指定した文字数まで読み込む」か「改行(エンター)が入力される」のどちらか早い方で読み込みを終了します。以下の例では、5文字入力されたタイミング、または改行されたタイミングで1回ずつ処理(echo)が行われます。

code
while read -r -n 5 line; do
  echo "line='${line}'"
done << 'DATA'
1234567
abc
DATA
terminal
line='12345'
line='67'
line='abc'

read -N 文字数 指定した文字数まで読み込む

-N オプションは、改行を区切りとして扱わず、純粋に「指定した文字数」を読み込むまで待ち続けます。改行そのものも「1文字」としてカウントされるのが特徴です。

code
# 改行を含めて正確に5文字ずつ読み込む例
# (12\n34 で5文字、567\n で5文字、合計10文字)
while read -r -N 5 line; do
  printf "line=%q\n" "${line}"
done << 'DATA'
12
34
567
DATA
terminal
line=$'12\n34'
line=$'\n567\n'