UNIX的なアレ

UNIX的なこととかいろいろ

初めてシェルスクリプトを書くときに知っておきたいこと

他の言語をを書き慣れてから、シェルスクリプトを書こうとするとしょうもない部分でハマったりすることがあります。「簡単な処理だからシェルスクリプトで書くか」と思っても無駄に時間がかかってしまっては仕方ないですよね。
今回は初めてシェルスクリプトを書くときに、ハマりそうな点について説明をしたいと思います。

実行権限をつける

単純にファイルを生成しても下記の用に実行しようとしても実行はできません。

# touch test
# ./test
sh: ./test: Permission denied

# touch foo
# ./foo
sh: ./foo: Permission denied

これはファイルに実行権限がついていないため、起きる現象です。
chmodコマンドを使って実行権限をつけてやりましょう。

# chmod +x test
# ./test

# chmod +x foo
# ./foo

エラーメッセージは返らずに、無事実行できたと思います。
まずシェルスクリプト用のファイルを生成したら実行権限をつけるようにしましょう。

実行するプログラムを指定する

よくおまじないと言われる記述部分です。これは、ここにおいてあるプログラムを使用してそれ以降を実行させましょうという意味になります。
今回はBourneShellを想定していますので下記のように指定をしましょう。

#!/bin/sh

Perlを書いたことある人なら似たような記述をしたことがあると思います。

#!/usr/bin/perl
#!/usr/local/bin/perl
#!/usr/bin/env perl

これは指定したPATHのPerlを使用しなさいって意味なので、shに関しても同様になります。

変数をセットする

自分はこれが最初はハマりました・・・かなり基本的な部分ですが他の言語とことなるので意識をしておく必要があります。
まず簡単なサンプルコードを以下に記載します。

#!/bin/sh
foo="hoge"
echo $foo

ここでのポイントは2点あります。

  • 変数をセットする時の=(イコール)の前後にはスペースをいれないこと
  • セットするときには、$はつけないが、展開するときには$をつけること

まず1点目は、他の言語を書き慣れている方にはかなり特殊に感じる部分だと思います。ここに半角スペースを1個でも入れてしまうとエラーが発生して、シェルスクリプトは動作しません。
次に2点目ですが、代入するときと利用するときで$をつける、つけないが変わってくるのでこれもしっかりと覚えておきましょう。

条件判定をさせる( testコマンド を使用する )

シェルスクリプトでも他の言語と同様に、ifを使って条件分岐をさせることができます。
if文で条件を記載するtest部分(スクリプト上は [ ] で記述する)部分の記述方法が特殊なのでこちらも押さえておく必要があります。
サンプルは以下のコード。

#!/bin/sh
touch TESTFILE
if [ -e TESTFILE ]
then
echo "OK"
else
echo "NG"
fi

上記は2行目でtouchしたTESTFILEというファイルが存在するかどうかをチェックして条件分岐をさせている例になります。
次は数値の比較の例を示したいと思います。

#!/bin/sh
NUM=0

if [ $NUM -eq 0 ]
then
echo "OK"
else
echo "NG"
fi

上記はNUMに代入した数値と0を比較しています。-eqという記述が数値比較となっているのが分かるかと思います。
こちらも独特な記述ですね。
testコマンドのオプションを簡単にまとめたいと思います。主に自分がよく使うものなので、一部です。

  • ファイルのチェック
-e ファイル名
指定したファイルがあれば真
-s ファイル名
指定したファイルサイズが0より大きければ真
  • 文字列チェック
-n 文字列
文字列の長さが0より大きければ真
文字列 = 文字列
2つの文字列が等しければ真
文字列 != 文字列
2つの文字列が等しくなければ真
  • 数値のチェック
数値1 -eq 数値2
2つの数値が等しければ真
数値1 -ge 数値2
1が数値2以上であれば真
数値1 -gt 数値2
数値1が数値2より大きいのであれば真
数値1 -le 数値2
数値1が数値2以下であれば真
数値1 -lt 数値2
数値1が数値2未満であれば真
数値1 -ne 数値2
2つの数値が等しくなければ真

また、先ほどの変数に代入する際の=の記述と同様にこちらも半角スペース縛りがあります。

[ 変数 -eq 数値 ]

と記述する際、[の直後と]の直前には半角スペースを入れてください。これを入れないと動作しません。

コマンドの実行結果を利用する

コマンドの実行結果を変数に代入したいときは、そのコマンドを`(バッククオート)で囲みその結果を変数に入れます。
サンプルコードは以下。

#!/bin/sh
HOSTNAME=`hostname`
echo $HOSTNAME

上記はhostnameというコマンドを一度変数に代入をしてから表示をさせている例になります。

Lockファイルを生成する

シェルスクリプトでおおいパターンがcronで定時に実行するというパターンだと思います。しかしながら、Backupの処理などある程度長時間かかることが想定される際は、2重起動をさせないようにしておく必要があります。
そういった際は、起動時にlockfileを生成し、終了時に削除をするようにし、そのファイルが存在するときは起動をさせないようにします。
サンプルコードは以下。

#!/bin/sh
LOCKFILE=/tmp/.lock
if [ -e $LOCKFILE ]
then
echo "Already Running"
else
touch $LOCKFILE
echo "OK"
sleep 10
rm $LOCKFILE
fi

上記の記述では、起動時にLockファイルをチェックしファイルが無かった場合のみに処理を実行させ最後にLockファイルを削除してLockを解除しています。
(かなり簡単すぎますか)この記述方法で2重起動を防ぐことができます。

コマンドの終了ステータスをチェックする

事前に実行したコマンドの結果によって処理を分けたいときがあると思います。そんな時は、$?をつかって事前のコマンドの終了ステータスをチェックしましょう。
サンプルコードは以下。

#!/bin/sh
host google.jp > /dev/null
echo $?

host hoge > /dev/null
echo $?

上記のコードを実行すると、下記のように表示されると思います。

0
1

これはコマンドの実行結果によって返ってくる値が変わってくるためです。
testコマンドと組み合わせて使えばば事前のコマンドの終了ステータスをとることによって条件を分岐させて、などというようなこともできます。

最後に

シェルスクリプトを書く機会は人によってはほとんど無いかもしれませんが、Unix系のサーバーを運用する人にとっては頻繁に書く機会があると思います。
ただ、個人的にはシェルスクリプトは時間はあまりかけず勢いで書いてしまうのでできるだけ下らないことではハマりたくないと思っています。
そんな時のためにこのエントリーが少しでも参考になれば幸いです。