シェルスクリプトを書く時に気をつけている事など

More than 5 years have passed since last update.

あくまでも個人的な意見であって、自分がシェルスクリプトを書く時に気をつけている事を備忘録的に列挙しているだけです。

「こうするべきだ」と押しつけている訳ではありません。

勿論「私はこうしている」とか「こうすると良いよ」という意見や議論は大歓迎です。


export (1) しないシェル変数は全て小文字で書く。

PATHHOME などは環境変数なので大文字で。

スクリプト内で使っているのは単なるシェル変数なので小文字で。

使い方をちゃんと区別する事は重要だと思う。


シェルの変数展開を活用しよう

[ -z "${foo}" ] && foo="FOO"

という書き方でも問題ないけど、

${foo:-FOO}

と記述した方がすっきり記述できるし意味が判りやすいと思う。


trap (1) を活用しよう

trap (1) で 0 を指定するとスクリプトが正常終了した時にも指定した処理が実行されるので、プログラム中で作成した一時ファイルの削除などにはもってこい。

スクリプトの先頭で

tmpdir="${TMP:-/tmp}/`basename ${0}`.$$"

mkdir -p ${tmpdir}
trap 'rm -r ${tmpdir}' 0

こんな処理をしてスクリプト中で使う一時ファイルなどは全て ${tmpdir} 以下に作成する様にすると後始末も楽で便利。


コマンドを使う場合はコマンドの持つ機能を活用しよう

grep (1) や cut (1) と awk (1) や sed (1) を組み合わせて使うのは、殆どの場合において CPU とプロセスと時間の無駄遣い。

勿論あまりにも複雑な正規表現が必要な場合や処理が煩雑になる場合は勿論この限りではないです。

grep "^HOGEHOGE" file | awk 'BEGIN { FS = "="}; { print $2 }'

というコード。一見何も問題ない様に見えるし確かに動作はするが、 awk (1) には拡張正規表現が実装されているで、

awk 'BEGIN{FS="="}/^HOGEHOGE/{ print $2 }' file

とすれば記述が簡潔になるし grep (1) の分だけ CPU やプロセス、時間が節約できる。

sed -n '/^HOGEHOGE/s/^.*=//p' file

としても等価なのでどちらを使うかは個人の好みで。


エラーメッセージは標準エラー出力に出そう。

unix を使う上では基本中の基本です。


インデント幅はスクリプト内で統一しましょう。

Tab でも スペース何個でも OK ですが、 1ファイルの中のインデント位は合わせないと恥ずかしいです。


${PATH} は信用せずにフルパスをシェル変数に代入して使う様にしましょう。

たまに ${PATH} に "." を含めるセキュリティ意識の低い困ったちゃんがいるので、スクリプト中で使用するコマンドは

foo="/path/to/foo"

:
${foo}

な感じで使うが良いと思います。

スクリプトの開発中は

foo="echo /path/to/foo"

こんな感じに変更しとくと mv (1) や rm (1) の様なコマンドの実行を抑止できるので初期段階では特に重宝すると思います。

logger="/usr/bin/logger -p daemon.err -t ${myname}"

なんて設定しとくと共通のロギング処理にもなるので安全便利。

一部の linux ディストリビューションの場合 /bin/sh が bash だったりするので、設定によっては cp (1) や rm (1) などが、 cp -i とか rm -i などの様な迷惑千万な alias に設定されていてもスクリプトの動作に支障をきたさなくなります。

シェルの内部コマンドも含め全てのコマンドは execv (2) 可能でなければならないと POSIX で定義されているので、 POSIX 準拠を詠っている OS であれば全てのコマンドが揃ってる(筈)。


複数行に渡る長いメッセージや固定データは echo (1) ではなくてヒアドキュメントを。

echo "………" > ${output}

echo "………" >> ${output}
echo "………" >> ${output}

などと書いていると途中行を修正する時に ">>" をついうっかり ">" にしてしまったりする間違いをする危険性が高いし、それぞれの echo (1) の実行ごとにファイルのオープンクローズが繰り返されてしまい非常に効率が悪いので、

cat << EOF> ${output}

………
………
………
EOF

と記述する様にすれば安心確実だしシステムにも優しいです。


スペースで区切られたデータの構文解析は set (1) を活用しよう。

年、月、日 をそれぞれ $year$month$date に代入する処理を素直に書けば

date="`date '+%Y %m %d'`"

year="`echo $date | awk '{ print $1 }'`"
month="`echo $date | awk '{ print $2 }'`"
day="`echo $date | awk '{ print $3 }'`"

な感じになるが date (1)、echo (1)、awk (1) の呼び出しが煩雑になる。それならば

set -- `date '+%Y %m %d'`

year="$1"
month="$2"
day="$3"

の方が CPU やプロセス、時間の節約になるし、見た目もすっきりすると思う。勿論位置パラメタは上書きされるので必要な値は事前にシェル変数に保存する。


bash や zsh に依存した処理は絶対使わない

折角移植性が優れているシェルスクリプトなので、なるべく posix に準拠した機能だけでスクリプトを作る様に心がけたいと思う。