シェルスクリプトを書く際の心得


概要

シェルを書く時のMY心得のようなものを書き殴ってみた。あまり洗練されてません^^;。
プログラミング一般に通じるところもあるかも。

こちらの投稿に触発されました。
シェルスクリプトコーディングルール@mochidamochikoさん)

心得1.適切な変数化、関数化

<変数について>
例えば、hostsから、あるホスト名のIPアドレスを抽出する処理があるとする。この場合、以下が適切な変数の使い方だ。

①適切な変数の使い方
host=$1
grep ${host} /etc/hosts | awk '{print $1}'

これを以下のようにする人がいる。

②やり過ぎな例
host=$1
config=/etc/hosts
grep ${host} ${config} | awk '{print $1}'

違いがわかるだろうか。①に比べ②は「何だか読みづらい」と感じるはずだ。
変数は値が見えないためどうしても変数名から内容を推測せざるを得ず、適切な名前を付けないと、コードの可読性を低下させる要因になってしまう。

適切な名前かどうかは、そのコマンドライン全体を見て何をやっているかを、変数の値を見なくてもわかるかどうか、が基準になる。意味が取れなければ変数名が適切でない。
上記の例だと、①はhostsに対しホスト名をキーにgrepしようとしていることがわかる。おそらくIPアドレスを抽出しようとしているのだろう、と想像できる。
しかし、②ではそもそも何をしようとしているのか分からない。grepコマンドの理解に必要な、処理対象のファイル名を変数configが隠してしまっているためだ。
コマンドの意図の理解を邪魔しないようにするには、変数configがhostsを指定しているということが分かるようにしなくてはならない。付けるなら、おそらくhostsという名前になるだろう。
その名前じゃ直接指定しても同じじゃん!変数化する必要ないじゃん!その通りだ。だから、②のコードは間違いなのである。
ただ、ファイル名の変数化が全くの無駄というわけではない。そのファイルが複数あり、切り替えて使ったりする場合は変数化する余地がある。

<関数について>
シェルスクリプトにおける関数はコマンドと同等であるため、いっぱしのUnixコマンドとみなせる設計とすること。
つまり具体的な単機能を提供しフィルタとして扱える必要がある。

心得2.コードの最後まで到達する処理を戻り値0(正常終了)としてコーディングする

運用シェルでれば、基本、構造化プログラミングを意識した作りにすると読みやすいコードになる。

シェルを書く時の基本的な流れ
#!/bin/sh
if; then
    exit 8  # 異常終了1
fi
if; then
    exit 16 # 異常終了2
fiexit 0      # 正常終了

心得3.変数宣言の意味は値のセットだけにあらず

標準のスコープがグローバルだからといって、変数を事前に宣言しておかないと、読む人を混乱させるコードになる。
制御構文や関数の内外で使われる変数は、セットする値が無いとしても事前に宣言しておく。

例えば…
val=        # 値は空でいいので、これ以降で使いますよと宣言しておく
if; then
    val=`` # 制御文の外で使うのだから、ここでいきなり登場させない
fi
echo ${val}

心得4.判定処理は一行に詰め込み過ぎない

if文を使うより||、&&などで一行にまとめた方が読みやすいのは確か。でも読みやすいのは1〜2個の連結まで。3つも4つもコマンドを連結するとめちゃくちゃ読みにくくなる。判定に用いる場合は過度にコマンドを連結しないこと。もしくは適度に改行を入れること。
また、同じ理由で if grep …; then はコマンドの長さと相談する。場合によっては以下のように分割する。

このように分割した方が良い場合もある
grepif [ $? -eq 0 ]; then

心得5.正規表現を乱用しない

grepやsedは重い。それは正規表現のマッチ処理自体が重いから。表現できるものが多ければ多いほどその仕組みの処理は重くなる。当たり前のことだ。
正規表現はあまりに何でも表現できるものだから、できる限り正確に、汎用的に表現しようと事細かく指定する人がいる。でも運用を考えればありえない指定だってある。それは意味のある細かさか?無駄に処理を重くしていないか?と必ず自分に問うようにしよう。

心得6.異なる制御文・コマンドを織り交ぜる

可読性のため。UNIX使いには当たり前のことかもしれないが、if文の中にif文を入れて、更にその中にif文を……などとすると、可読性が恐ろしく低下する(そもそもネストを重ねること自体あまり褒められたものではない)。
そういう場合、最後の判定を||や&&で1行にするとぐっと読みやすくなる。
読む側の気持ちになって、飽きの来ないコードにすること。

ダメな例
if; then
    if; then
        if; then
            exit 8
        fi
    fi
fi
上のコードよりは読みやすい例
if; then
    if; then|| exit 8
    fi
fi

心得7.汎用コマンドより専用コマンドを用いる

これも可読性のため。例えば、何でもかんでもawkで処理しない。awk '{print $1}' でなく、cut -d' ' -f1 とする。
awkを使いこなせる人はかなり少ない。ましてや読める人はもっと少ない。現実を見よう。(まあ、このくらいならわかると思うけどね)

心得8.運用シェルなら性能より可読性・移植性

いざシステムに障害が起きた時、シェルを見て処理を追うという場面は多い。その際、そのシェルが何をしているのか、一秒でも早く理解できるコードにすることが重要。
また、システム更改などでは旧システムや他システムのシェルが流用されることも多い。移植されることを考慮して、BourneShellと可能な限り互換性があるシェルを書く。

心得9.業務シェルならbash. bashを使うなら独自拡張の使用を厭わない

可読性はもちろん考慮する必要があるが、bashを使うなら他シェルとの互換性など意識せず、独自拡張をバンバン使うべき。でなければbashを使う意味がない。