シェルスクリプトの罠を避ける三つの tips

  • 356
    いいね
  • 1
    コメント

シェルスクリプトは、Unix 系環境で仕事をするエンジニアなら誰もが一度は書くであろうにもかかわらず、書き方や特性を熟知している人が少ない言語です。この記事は、シェルスクリプトを書くときに罠を踏まないようにするために最低限あなたが気を付けるべき tips 集です。「たかがシェルスクリプト」とは思わないでください。生半可に書かれたシェルスクリプトはあなたの (チームの) 生産性をかえって低下させます。

Shebang に bash を明示しろ

Bash でしか使えない機能のことを俗に Bashism と言います。Bashism はもちろん Bash 以外のシェルでは動きません。これに関するありがちな罠は、以下のように発生します。

  1. Bash が /bin/sh として使われている環境でシェルスクリプトを書く。うっかり Bashism がシェルスクリプトに含まれていても、 /bin/sh は何も文句を言わないので、Bashism に気付かない。
  2. /bin/sh が Bash でない環境1でそのシェルスクリプトを実行すると動かない。

これに対する回避策は、どの環境でも必ず Bash でシェルスクリプトが実行されるようにすることです。そのために、シェルスクリプトの先頭行には #!/bin/sh ではなく #!/bin/bash と書きましょう。

愚直な回避策として「Bashism を使わないように気を付ける」という方向性もアリですがそのためにはより多くの労力が必要となるので、最低限気を付けるべきことというレベルではおすすめしません。

Shellcheck でチェックしろ

Shellcheck という lint ツールがあります。シェルスクリプトを書くときに犯しがちな様々なミスを指摘してくれます。Haskell で書かれているので環境によっては若干インストールが面倒ですが、非常に有用なのでシェルスクリプトを書いたときは毎回これを使ってチェックしましょう。

set -Ceu しろ

シェルにはいくつかオプションが設定できるようになっていて、それによって実行時の挙動が変わります。ここでは、デフォルトでは有効になっていないがデバッグに役立つオプションを三つ紹介します。

-C

grep foo bar.log > filtered.log のようにリダイレクトを使うと、デフォルトでは出力先のファイルは問答無用で上書きされてしまいます。リダイレクト先のファイル名を打ち間違えるなどして意図せずファイルを上書きしてしまってもすぐには気付けません。-C オプションを有効にしておけば、リダイレクト先のファイルが既に存在している場合はエラーになるので、不用意な上書きを防げます。

なお、ファイルを本当に上書きしたいときは > の代わりに >| と書くと上書きされます。

-e

シェルスクリプトの途中でエラーが発生したときはそのシェルスクリプトの実行を中断すべき場合が多いでしょう。例えば、プログラムをビルドしてそれを特定のディレクトリーにインストールするというシェルスクリプトがあったとしたら、ビルドがもし失敗した場合にインストールを試みるのは無駄です。大概のコマンドは、失敗したときに 0 以外の終了ステータスを返すようになっているので、それを自動的に検知してシェルスクリプト自体も終了させる -e オプションを有効にしましょう。

なお、コマンドが失敗してもシェルスクリプトを終了させたくないときは、コマンドに && true を付けるとシェルスクリプトは終了しません。2

# make build が失敗しても make install を実行する。
# しかし make install が失敗したら make clean 以降は実行しない
set -e
make build && true
make install
make clean
...

-u

デフォルトでは、存在しない変数は空文字列に展開されます。これはしばしば悲惨な結果を招きます。例えば下のスクリプトでは変数の名前を打ち間違えているので ~/foo.tmp ではなく ~ が丸ごと削除されてしまいます。

tmpdirname=foo.tmp
rm -fr ~/"$tempdirname"

-u オプションを有効にしておけば、存在しない変数はエラーになってスクリプトが終了するので安心です。

オプションの指定方法

上記三つのオプションを有効にするには、スクリプトの冒頭 (#!/bin/bash の次の行) に set -Ceu と書きましょう。冒頭に書くことで、確実にオプションを有効にできます。

スクリプトを実行するときに bash -Ceu my_script.sh のようにコマンドライン上でオプションを指定することもできますが、指定し忘れると意味がないのでスクリプトの中でオプションを有効にする方が確実です。

まとめ

以上、シェルスクリプトを書くときに気を付けるべき三つのことを紹介しました。

ここに書いたのはスクリプトを書き慣れていない人がまともなスクリプトを書くためにまず最低限知るべきことにとどまっており、決してこれさえ気を付ければスクリプトが完璧になるというものではありません。「Bash 以外のシェルでもスクリプトを実行させなければならなくなったら?」「Shellcheck でも指摘されないミスにはどんなものがある?」「if 文の中では -e オプションはどのように働く?」などと考え出すと、気を付けなければならないことはいくらでも出てきます。

シェルスクリプトは一見誰でも簡単に書ける手軽なプログラミング言語ですが、実際は注意深く書かなければすぐ「動かないスクリプト」が出来上がってしまう厄介な言語です。この記事をシェルスクリプトの厄介さを認識するための第一歩として捉えてもらえれば幸いです。


ACCESS Advent Calendar 2016 では、ACCESS のエンジニアが毎日思い思いの記事を公開しています。明日は @bols_blue さんです。


  1. 最近では主に Debian と Ubuntu が該当 

  2. && true ではなく || true を付けるべきではと思いましたか? 実際のところ、-e オプションを局所的に無効化するだけならどちらでも構いません。ただし、|| true を使うと失敗したコマンドの終了ステータスを $? 変数で取ることができなくなります。