スクリプトを書く

INPUT · スライド

終了ステータス

01 / 08

見えないところで返っていたもの

あなたは章4から、これを何度も打ってきたね。

~ $ echo $?0

$?直前のコマンドが返した数だよ。

0        うまくいった0 以外   うまくいかなかった

そして &&|| も使ってきたでしょう。

mkdir hako && cd hako          うまくいったら次cd hako || echo "dame"         だめだったら

3本目で if をやったときにも出てきたね。

if grep -q aka f; then …       grep の成否で分かれる

全部、同じ1つの仕組みなんだ。今日はそれを整理するよ。

> すべてのコマンドは、終わるときに数を1つ返している

この数を終了ステータスと呼ぶよ。ふだんは見えないけれど、いつも流れているんだ。

今日やることは3つだよ。

1. 返ってくる数を読む($?)2. 自分の道具から返す(exit)3. 失敗に強い書き方(set -e など)

章12で「見ていないあいだも動かす」をやるでしょう。そのとき、失敗を自分で気づける道具が必要になるんだ。今日はその下ごしらえだよ。

02 / 08

0 が成功という決まり

不思議に思ったかもしれないね。ふつう 1 が「はい」でしょう。

0        成功1 以上   失敗

なぜ逆なのかというと、失敗の理由がいくつもあるからだよ。

0    成功(1通りしかない)1    ふつうの失敗2    使い方がまちがっている3    ファイルが無い…    道具ごとに決めてよい

成功は1つだけれど、失敗はいろいろでしょう。だから成功に 0 を割り当てて、残り全部を失敗に使うんだ。うまい決め方だよね。

試せるコマンドが2つあるよ。

~ $ true; echo $?0~ $ false; echo $?1

trueいつも成功するだけのコマンド、falseいつも失敗するだけのコマンドだよ。何もしないけれど、練習と組み立てに使うんだ。

1つ注意があるよ。$?直前の1つしか覚えていないんだ。

~ $ false~ $ echo $?1~ $ echo $?0        ← さっきの echo が成功したので 0!

2回読むと消えてしまうでしょう。だから使うなら、変数にしまうんだ。

RC=$?

これで何度でも使えるね。

03 / 08

&&|| の正体

この2つは、終了ステータスを見ているだけなんだ。

A && B      A が 0 なら B をやるA || B      A が 0 以外なら B をやる

&& は成功でつなぐ、|| は失敗でつなぐだね。

~ $ true && echo yoiyoi~ $ false || echo damedame

これで、いままで打ってきたものが説明できるでしょう。

mkdir hako && cd hako  ↓ mkdir が 0 を返したら cd する  ↓ 失敗したら cd しない(大事!)

失敗したのに次へ進まないのが値打ちなんだ。もし ; でつないだら、こうなるよ。

mkdir hako; cd hako  ↓ mkdir が失敗しても cd しようとする

3つのつなぎ方を並べておくね。

つなぎ意味
;成否を気にせず、次をやる
&&成功したら、次をやる
`\\`失敗したら、次をやる

組み合わせも書けるよ。

cp a b && echo "utsushita" || echo "shippai"

うまくいったらこれ、だめならこれという形だね。if を書かなくても済むでしょう。

ただし長くなると読みにくいので、3つ以上つなぐなら if にするといいよ。

04 / 08

パイプの成否は最後だけ

ここは知っていないとはまるところだよ。

~ $ false | true; echo $?0~ $ true | false; echo $?1

最後のコマンドの成否だけが返るんだ。途中が失敗しても分からないでしょう。

これが困る場面があるよ。

cat nai.txt | grep aka > kekka.txt  ↓ cat は失敗している(ファイルが無い)  ↓ でも grep は動いたので 0 が返る「うまくいった」と思ってしまう

気づけない失敗だね。章4でやったパイプの弱点なんだ。

対策は3つあるよ。

1. 前もって確かめる

[ -f nai.txt ] || exit 1cat nai.txt | grep aka

3本目でやった「入り口の守り」だね。いちばん素直だよ。

2. パイプを使わない

grep aka nai.txt > kekka.txt

そもそも cat が要らないでしょう。章4でやった「cat の無駄」だね。パイプを減らせば、この問題も減るんだ。

3. 中身を確かめる

[ -s kekka.txt ] || echo "kekka ga kara"

結果が空かどうかを見るんだ。3本目でやった -s だね。

知っていれば避けられる問題だよ。「パイプの $? は最後だけ」と覚えておいてね。

05 / 08

自分の道具から返す

自分のスクリプトも、ちゃんと返さないといけないよ。

#!/bin/shexit 3
~ $ ./a.sh; echo $?3

exit に書いた数がそのまま返るね。書かないとどうなるかというと、

#!/bin/shecho x
~ $ ./b.sh; echo $?0

最後の行の成否が返るんだ。echo が成功したので 0 だね。

使う数の目安はこうだよ。

0     うまくいった1     ふつうの失敗2     使い方がまちがっている

0 と 1 だけ使っていれば十分なことが多いよ。細かく分けるのは、呼ぶ側が区別したいときだけだね。

1つ落とし穴があるよ。大きい数は使えないんだ。

~ $ sh -c 'exit 300'; echo $?44

300 が 44 になったでしょう! 使えるのは 0 から 255 までなんだ。

300 - 256 = 44

256 で割った余りが返るんだよ。だから小さい数を使うのが安全だね。

そして章8の話とつながるよ。

128 + シグナル番号 = 強制終了されたときの数
Ctrl-C(INT は 2)    →  130kill -9(KILL は 9)  →  137

130137 を見たら「止められたんだな」と分かるんだ。だから自分で使うのは小さい数にしておくと、混ざらなくてよいでしょう。

06 / 08

失敗したら止まる(set -e

スクリプトは、失敗してもそのまま次へ進んでしまうんだ。

#!/bin/shcd /nai-bashorm -r ./*          ← 元の場所で動いてしまう!

恐ろしいでしょう。cd が失敗したのに rm が動くんだ。

防ぎ方が2つあるよ。

1. && でつなぐ

cd /nai-basho && rm -r ./*

2. set -e を書く

#!/bin/shset -ecd /nai-bashorm -r ./*          ← ここまで来ない

set -e は「どれか失敗したら、そこで止まる」という指示だよ。

~ $ ./d.sh; echo rc=$?rc=1

false の次の echo が動かなかったでしょう。

仲間が3つあるよ。

書き方すること
set -e失敗したら止まる
set -u無い変数を使ったら止まる
set -x打った行を見せる(デバッグ)

set -u も強いよ。

#!/bin/shset -urm -r "$KESU"/*     ← KESU が空なら、ここで止まる

2本目でやった「引数を忘れると rm -r /* になる」を防げるでしょう。

だから、こう書き始めるのが作法になっているんだ。

#!/bin/shset -eu

危ないスクリプトの1行目だと思ってね。

07 / 08

見せる(set -x)と後片づけ(trap

スクリプトが思ったように動かないとき、これを使うよ。

#!/bin/shset -xecho mieru
+ echo mierumieru

打っている行が + 付きで見えるでしょう。変数が何に広がったかまで分かるので、原因がすぐ見つかるんだ。

set -x       ここから見せるset +x       ここでやめる

- で始めて + でやめるんだ。おかしいところだけ挟むといいよ。

もう1つ、後片づけの道具があるよ。

#!/bin/shtrap "echo katazuke" EXITecho hontai
hontaikatazuke

trap は「終わるときに必ずこれをやって」という指示だよ。

何に使うかというと、途中で作ったものの片づけだね。

#!/bin/shtrap "rm -f /tmp/shigoto.$$" EXITecho x > /tmp/shigoto.$$… 何か作業 …

途中で失敗しても、ゴミが残らないでしょう。$$ は自分のプロセス番号だよ(章8でやったね)。

章8で覚えたシグナルも指定できるんだ。

trap "echo tomerareta" INT     Ctrl-C されたときtrap "echo katazuke" EXIT      どんな終わり方でも

EXIT が便利だよ。成功でも失敗でも必ず通るからね。

章8の知識が、スクリプトを書くときの道具になったでしょう。

08 / 08

さあ、打ってみよう

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

echo $?              直前の成否を見るRC=$?                しまっておくtrue / false         成功だけ / 失敗だけA && B               成功したらA || B               失敗したらexit 1               失敗として終わるset -e               失敗したら止まるset -u               無い変数で止まるset -x               打った行を見せるtrap "…" EXIT        終わるとき必ずやる

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

1. $? は直前の1つだけ(2回読むと消える)2. パイプの $? は最後のコマンドのもの3. exit は 0〜255 まで(300 は 44 になる)

そして今日の結論だよ。

> 成否を返す道具は、他の道具とつなげられる

ちゃんと exit 1 を返すスクリプトは、こう使えるんだ。

./tenken.sh && echo "mondai nashi"./tenken.sh || echo "abunai!"

あなたの道具が、&& の列に入れるようになったでしょう。grepmkdir と同じ仲間になったんだ。

これは章12でとても大事になるよ。

見ていないあいだに動かす  ↓ 失敗したかどうか、あとで分かる必要がある  ↓ 終了ステータスで残す

章11も次で最後だね。関数(手順に名前を付ける)をやるよ。では打ってみよう。