スクリプトを書く

INPUT · スライド

`while` と `read`

01 / 08

数が決まっていないとき

for は「並んでいるもの」を回したね。

for F in *.txt; do … done      ファイルの数だけfor N in $(seq 1 5); do … done  5回

どちらも始める前に回数が決まっているでしょう。

でも、こういうときはどうしよう。

ファイルの中身が終わるまで、1行ずつ読みたい空きが 90% を下回るまで、待ちたい

回数が分からないね。こういうときは while を使うんだ。

while 条件; do  やることdone

読み方は「条件が成り立つあいだ、繰り返す」だよ。

for     並んでいるものを、順にwhile   条件が続くあいだ、ずっと

今日はもう1つ、大事な道具を覚えるよ。

while read L; do  echo "[$L]"done < hyou.txt

read1行読むコマンドだよ。これと while を組み合わせると、行を1行ずつ、正しく扱えるようになるんだ。

実はこれ、for ではきれいにできないことなんだよ。理由はあとで見るね。

02 / 08

数えながら回す

まず素朴な形を見ておこうね。

I=1while [ $I -le 3 ]; do  echo "n=$I"  I=$((I+1))done
n=1n=2n=3

3つの部品があるよ。

I=1                 始めの値while [ $I -le 3 ]  続ける条件I=$((I+1))          進める

この3つがそろわないと止まらないんだ。I=$((I+1)) を忘れると、永遠に回り続けるよ。

I=1while [ $I -le 3 ]; do  echo n=$I        ← I が増えない!done

これが無限ループだね。そうなったら Ctrl-C で止めるんだ。章8で覚えた止め方が、ここで役に立つよ。

$((…)) は章10でやった計算だね。

I=$((I+1))     I に1を足す

この形は正直、あまり使わないんだ。for N in $(seq 1 3) のほうが短いからね。

for N in $(seq 1 3); do echo n=$N; done      短いI=1; while [ $I -le 3 ]; do … done          長い

回数が決まっているなら for を使ってね。while の本当の使いどころは、次のスライドからだよ。

03 / 08

while read で1行ずつ

これが今日いちばん大事な形だよ。

while read L; do  echo "[$L]"done < hyou.txt

read1行読んで、変数に入れるコマンドだよ。

read L  ↓ 1行読む  ↓ L に入れる  ↓ 読めたら 0、終わりなら 1 を返す

終わりで 1 を返すので、while が止まるんだ。うまい仕掛けでしょう。

while read L        読めるあいだ、回るdone < hyou.txt     どこから読むかは、ここで指定

< hyou.txtdone のうしろに書くのが独特だね。fordone > と対になっているよ。

done < f     ここから読むdone > f     ここへ出す

列に分けることもできるよ。

while read IRO KAZU; do  echo "$IRO wa $KAZU"done < hyou.txt
aka wa 3ao wa 2ki wa 1

空白で分けて、順に入れてくれるでしょう。cut を使わなくてもいいんだ。表のようなファイルを扱うときに便利だよ。

04 / 08

for では割れてしまう

ここが while read を使う理由だよ。空白入りの行で比べてみるね。

kuuhaku.txt の中身はこれ。

aru monohoka

for で回すと

~ $ for L in $(cat kuuhaku.txt); do echo "[$L]"; done[aru][mono][hoka]

3回になってしまったね。行が割れたでしょう。

while read なら

~ $ while read L; do echo "[$L]"; done < kuuhaku.txt[aru mono][hoka]

2回だね。ちゃんと行のまま読めたよ。

理由は章10でやったことだね。

$(cat f)     広がったあと、空白で割られるread         行の区切りで読む(空白は気にしない)

割る基準が違うんだ。

> 行を扱うなら while readfor $(cat …) は使わない

これは覚えておく値打ちがあるよ。ファイル名や人の名前には空白が入ることがあるでしょう。for で書くと、そういうときだけ壊れるんだ。テストでは見つからず、本番で壊れるやつだね。

for      並んだ「単語」を回す道具while read  並んだ「行」を回す道具

相手が行なら while read、と覚えてね。

05 / 08

最後の行に改行が無いと

while read に1つだけ癖があるよ。

nonl.txt の中身はこれ(最後に改行が無い)。

ichinisan          ← ここに改行が無い

読んでみるね。

~ $ while read L; do echo "[$L]"; done < nonl.txt[ichi][ni]

san が読まれないでしょう! これは驚くところだよ。

理由はこうだよ。

read は「改行まで」を1行として読む  ↓ san のうしろに改行が無い  ↓ 「行が完成していない」と見なされる

これは Unix の考え方なんだ。

> テキストファイルの行は、改行で終わっている

echoprintf\n を書けば、ちゃんと改行が付くよ。だから自分で作ったファイルなら心配ないんだ。

困るのは、他から来たファイルだね。確かめ方はこれだよ。

~ $ tail -c1 nonl.txt | od -c | head -10000000   n

最後の1文字が \n でなければ、改行が無いということだよ。

直し方は簡単だよ。

echo "" >> nonl.txt      改行を足す

知っていれば、すぐ直せるでしょう。知らないと「1行足りない」で半日悩むところだよ。

06 / 08

パイプで渡すと変数が消える

もう1つ、大きな落とし穴があるよ。同じことを2通りで書くね。

パイプで渡す

N=0cat hyou.txt | while read L; do  N=$((N+1))doneecho "soto=$N"
soto=0

0 だ! 3行読んだのに増えていないでしょう。

リダイレクトで渡す

N=0while read L; do  N=$((N+1))done < hyou.txtecho "soto=$N"
soto=3

こちらは 3 だね。

理由は章10でやったことだよ。

パイプのうしろは、子シェルで動く  ↓ 子シェルの中で N が増える  ↓ 子シェルが終わると、消える

sh script.sh. script.sh の違いと同じ話だね。子シェルでやったことは、親に戻ってこないんだ。

だから作法はこうだよ。

> while readdone < f で渡す。パイプで渡さない

どうしてもパイプで渡したいときは、結果を出力で受け取るんだ。

N=$(cat hyou.txt | grep -c .)

変数で持ち帰るのではなく、出力で持ち帰るんだね。章10で覚えた $(…) が、ここでも効くでしょう。

この落とし穴は、シェルを使う人がみんな一度はまるところだよ。理由まで分かっていれば、迷わないね。

07 / 08

until と無限ループ

while の逆もあるよ。

I=0until [ $I -ge 3 ]; do  echo "i=$I"  I=$((I+1))done

until は「条件が成り立つまで」だよ。

while 条件   条件が真のあいだ回るuntil 条件   条件が偽のあいだ回る

同じことが書けるので、読みやすいほうを選ぶといいね。

while [ $I -le 3 ]      3 以下のあいだuntil [ $I -gt 3 ]      3 を超えるまで

どちらでもいいけれど、while のほうがよく使われるよ。

そして最後に、わざと無限ループを作る形も見ておくね。

while true; do  date  sleep 5done

true は「いつも成功する」コマンドだよ。だから永遠に回るんだ。

見張りを作るときに使うよ。

5秒ごとに調べて、変わったら知らせる

止め方は Ctrl-C だね。章8で覚えたやり方だよ。

Ctrl-C   止める(INT)Ctrl-Z   一時停止(TSTP)

ただし、これを本番で使うことはあまりないんだ。章12でやる cron のほうがよいからだよ。

while true + sleep   端末を閉じると止まるcron                 いつでも動く

道具の向き不向きだね。手元で様子を見るなら while true、ずっと動かすなら cron だよ。

08 / 08

さあ、打ってみよう

この回で使う形をまとめておくね。

while [ $I -le 3 ]; do … I=$((I+1)); done   数えて回るwhile read L; do … done < f                行を1行ずつwhile read A B; do … done < f              列に分けてuntil [ 条件 ]; do … done                  ~まで回るwhile true; do … sleep 5; done             ずっと回る

覚えておく落とし穴は3つだよ。

1. 最後の行に改行が無いと読まれない2. パイプで渡すと変数が外に残らない3. 進める行を忘れると止まらない

そして今日の結論はこれだね。

> 行を扱うなら while read

for $(cat f)      × 空白で割れるwhile read < f    ○ 行のまま読める

章11もあと2本だよ。

次: 終了ステータス($? と && と ||)最後: 関数(手順に名前を付ける)

次の回は、実はもう何度も使っているものだよ。$?&&|| も打ってきたでしょう。あれをきちんと整理する回なんだ。

では回してみよう。