拡張 POSIX シェルスクリプト Advent Calendar 2013、13日目の記事です。
今日のネタは組込みコマンド read
の解説と使い方を紹介します。
read
コマンドとは?
read
の基本仕様・動作は次の通りです。
- シェルの組込みコマンドである。
- 標準入力を読み、一行読み込むか EOF になると終了する。
- 終了コードは、行を読み込めたときは 0、 読み込めなかったとき (EOF も含む) は 1 となる。
- 引数にシェル変数名が指定されていたら読み込んだ行を変数に格納する。 読み込んだ行の末尾に改行が付いていたら削除した値となる。
- 引数のシェル変数名が複数指定されていたら区切り文字
(デフォルトはスペースとタブ。シェル変数
$IFS
の値) で分割して変数に格納する。 - 入力の区切り文字はバックスラッシュ
\
で分割をエスケープすることができる。
代表的なオプションは次の通りです。
-r
- バックスラッシュ
\
によるエスケープを無効にしてそのまま解釈する。 - 区切り文字のエスケープはできなくなる。
- バックスラッシュ
-t タイムアウト秒
- 入力が指定された秒の間に得られなかったときにタイムアウトする。
- 古めの ksh 93 以前は対応していない。
以下に使い方の例を示します。
(ksh の場合、組込みの echo
コマンドの動作が環境に依存して異なるため、
echo -E 〜
の代わりに print -r 〜
を用いてください)
単純な入力と受け取り
特に難しくはないですね。
$ read i1 i2 <<<'foo bar'; echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[bar]
連続した区切り文字はまとめて一つの区切りとして扱われます。
$ read i1 i2 <<<'foo bar'; echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[bar]
read
はユーザーの確認待ちにも利用されることがあります。
#!/bin/sh
echo '◯◯処理を開始します。Enter キーで継続します。(中断は Ctrl+C)'
read
## …何かの処理…
echo '◯◯処理が完了しました。'
exit 0
#!/bin/sh
echo '◯◯処理を開始しますか? (yes or no) '
read answer
case "$answer" in
yes)
: OK
;;
*)
echo '中断します。'
exit 1
;;
esac
## …何かの処理…
echo '◯◯処理が完了しました。'
exit 0
入力の語数 vs. 指定された変数の数
入力の語数のほうが少ない場合、語が足りない分の変数は空になります。
$ i1=xxxx; i2=yyyyy
$ read i1 i2 <<<''; echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[] i2=[]
$ i1=xxxx; i2=yyyyy
$ read i1 i2 <<<'foo'; echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[]
入力の語数のほうが多い場合、残りの語が最後の変数にまとめて入ります。 残りの語の直前と以降の区切り文字は維持されます。
$ echo foo bar baz
foo bar baz
$ read i1 i2 < <(echo foo bar baz); echo "i1=[$i1] i2=[$i2]"
i1=[foo] i2=[bar baz]
$ echo 'foo bar baz'
foo bar baz
$ read i1 i2 < <(echo 'foo bar baz'); echo "i1=[$i1] i2=[$i2]"
i1=[foo] i2=[bar baz]
EOF
入力がなければ変数は空になり、終了コードは 1 になります。
$ read i1 i2 </dev/null; echo -E "status=$? i1=[$i1] i2=[$i2]"
status=1 i1=[] i2=[]
入力はあるが改行コードなしに EOF になった場合、 変数は設定され、終了コードは 1 はなります。
$ read i1 i2 < <(echo -n 'foo'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=1 i1=[foo] i2=[]
このため、次の例のように while
と組み合せて使用したときに、
最後の入力の処理が抜け落ちてしまう可能性があります。
行指向の入力を扱う場合は問題ありませんが、
そうでない入力が想定される場合は注意が必要です。
#!/bin/sh
while read foo bar; do
## …何かの処理…
done
区切り文字のエスケープ
区切り文字はバックスラッシュ \
でエスケープすることができます。
エスケープされた区切り文字はそのまま変数に設定されます。(\
はなくなる)
$ echo -E 'foo\ bar'
foo\ bar
$ read i1 i2 < <(echo 'foo\ bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo bar] i2=[]
$ echo -E 'foo\ bar'
foo\ bar
$ read i1 i2 < <(echo -E 'foo\ bar\n'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo ] i2=[bar]
$ echo -E 'foo \ bar'
foo \ bar
$ read i1 i2 < <(echo -E 'foo \ bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[ bar]
行末のバックスラッシュ \
は入力行の区切りをエスケープします。
このときは \
だけでなく改行文字もなくなり、次の行が連結されます。
$ echo -E 'foo bar\'; echo -E 'baz'
foo bar\
baz
$ read i1 i2 < <(echo -E 'foo bar\'; echo -E 'baz'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[barbaz]
エスケープの抑制
入力中のバックスラッシュ \
のエスケープ効果を抑制してそのまま変数に受け取るには
-r
オプションを指定します。このとき、区切り文字のエスケープはできなくなります。
$ echo -E 'foo\ bar'
foo\ bar
read -r i1 i2 < <(echo -E 'foo\ bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo\] i2=[bar]
$ echo -E 'foo\ bar'
foo\ bar
$ read -r i1 i2 < <(echo -E 'foo\ bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo\] i2=[bar]
$ echo -E 'foo\\ bar'
foo\\ bar
$ read -r i1 i2 < <(echo -E 'foo\\ bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo\\] i2=[bar]
区切り文字の指定
シェル変数 $IFS
に含まれる文字が区切り文字として利用されます。
$ echo -E 'foo : bar'
foo : bar
$ read i1 i2 < <(echo -E 'foo : bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[: bar]
$ IFS=: read i1 i2 < <(echo -E 'foo : bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo ] i2=[ bar]
$ IFS=': ' read i1 i2 < <(echo -E 'foo : bar'); echo -E "status=$? i1=[$i1] i2=[$i2]"
status=0 i1=[foo] i2=[bar]
ただし、改行文字は区切り文字には利用できません。
改行文字はデフォルトの $IFS
に含まれていますが、無視されます。
:
区切りのテキストデータである passwd
(5), group
(5) を処理する例:
$ getent passwd |while IFS=: read name x uid x; do
echo "name=$name, uid=$uid"
done
$ getent passwd |while read -d : name x uid x; do
echo "name=$name, uid=$uid"
done
入力をそのまま受け取る
入力をそのまま変数に受け取りたいとき、-r
オプションでエスケープを抑制して変数を一つだけ指定すれば済むと思うかもしれませんが、
それだけでは不完全です。 次の例のように行の先頭と末尾の区切り文字が失なわれます。
$ echo -E ' foo \ \\ bar \'
foo \ \\ bar \
$ read -r line < <(echo -E ' foo \ \\ bar \'); echo "status=$? line=[$line]"
status=0 line=[foo \ \ bar \]
$ echo -E 'foo bar '
foo bar
$ read -r line < <(echo -E 'foo bar '); echo "status=$? line=[$line]"
status=0 line=[foo bar]
さらに $IFS
を空にする必要があります。
$ echo -E ' foo \ \\ bar \'
foo \ \\ bar \
$ IFS= read -r line < <(echo -E ' foo \ \\ bar \'); echo "status=$? line=[$line]"
status=0 line=[ foo \ \ bar \]
$ echo -E 'foo bar '
foo bar
$ IFS= read -r line < <(echo -E 'foo bar '); echo "status=$? line=[$line]"
status=0 line=[foo bar ]
タイムアウトの判定
入力待ちをタイムアウトするには -t
オプションに待つ秒数を指定します。
-t 0.5
のように小数点以下の精度も指定可能です。
残念ながら、ksh 88 の read
は -t
オプションがありません。
#!/bin/sh
echo '◯◯処理を開始します。10秒経過あるいは Enter キーで継続します。(中断は Ctrl+C)'
read -t 10
## …何かの処理…
タイムアウトが発生したときの挙動はシェルの種類とバージョンに依存します。
- bash 4.0 以降
- 終了コードが 129 以上になる。(128 +
SIGALRM
のシグナル番号値) - 指定された変数は空になる。(
var=
されるのと等価)
- 終了コードが 129 以上になる。(128 +
- bash 3.x 以前
- 終了コードが 1 になる。
- 指定された変数は空になる。(
var=
されるのと等価) - EOF になった場合と区別がつかない。
- ksh, zsh
- 終了コードが 1 になる。
- 指定された変数は未定義になる。(
unset var
されるのと等価)
bash 4 以降と ksh, zsh であれば、次のようにタイムアウト時の判定ができます。
read -t 60 input
if [[ $? -gt 128 ]] || [[ -z ${input+set} ]]; then
## タイムアウト時の処理
fi
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013