情報処理 I - 第12回

今回の目標

  1. shスクリプトの作成
  2. アクセスカウンタの作成
  3. アクセスカウンタの組み込み
教科書243~250ページ

1.sh スクリプト

1.1 csh スクリプトの復習

この講義の第11回では,シェルで実行するコマンドをファイルに書いておき,そのファイルをシェルに読み込ませることでコマンドを実行させる方法について学びました.ただし,第11回では cat コマンドを使ってコマンドをファイルに書き込んでいましたが,シェルスクリプトはテキストファイルなので,普通これも emacs などのテキストエディタを使って作成します。

テキストエディタでシェルスクリプトを作成する
% emacs sample.csh &[Enter]

ファイル名の拡張子は,実はここでは何でもいいんですが(付けなくてもいい),csh のスクリプトなので一応 .csh という拡張子を付けることにします.emacs が起動したら,以下の内容をタイプしてください.

#!/bin/csh -f[Enter]
echo "今日は"[Enter]
date[Enter]
echo "です."[Enter]
cal[Enter]

タイプできたら C-x C-s でファイルを保存してください.このとき emacs を終了させないでおきましょう.次に,このファイルを実行可能に設定します.ターミナルのウィンドウで次のコマンドを実行してください.

シェルスクリプトを実行可能にする
% chmod +x sample.csh[Enter]

それでは,このスクリプトを実行してみましょう.

シェルスクリプトを実行する
% ./sample.csh[Enter]

このシェルスクリプトは csh によって実行されます.しかし csh のシェルスクリプトは,実はあまり一般的ではありません.Unix/Linux では多くのコマンドがシェルスクリプトで実現されていますが,そのほとんどが sh (Bourne shell) に対応したものになっています(Linux の場合,実際には sh と互換性のある bash (Bourne again shell) が実行されます).

1.2 sh スクリプトの作成

それでは sh 用のスクリプトを作成してみましょう.先ほど作成した csh 用のシェルスクリプトを,sh 用に変更してみます.emacs は sample.csh を編集している状態になっていると思いますから,これを一旦 sample.sh というファイル名で保存してください.emacs のバッファの内容を別の名前で保存するには,C-x C-w を使います.C-x C-w をタイプして,保存するファイル名に sample.sh を指定してください.

sample.sh として保存する
C-x C-w をタイプして,ファイル名に sample.sh を指定する.

保存が完了したら,バッファの最初の1行を,太字のように,csh ではなく sh を起動するように変更してください.

#!/bin/sh
echo "今日は"
date
echo "です."
cal

変更できたら C-x C-s でファイルを保存します.このときも emacs はバックグラウンドで動かしたままにしておいてください.そして,ターミナルのウィンドウで sample.sh を実行可能に設定したあと,実際に実行してみてください.

シェルスクリプトを実行可能にして実行する
% chmod +x sample.sh[Enter]
% ./sample.sh[Enter]

このスクリプトは sh で解釈されますが,単にコマンドを順番に実行するだけなので,結果は csh の時と同じはずです.それでは,この sh を対話的なシェルとして使って,実際の動作を確かめてみましょう.

1.3 sh をシェルとして使う

sh コマンドを実行すると,sh をシェルとして使用できるようになります.sh のプロンプトは,ここでは "$" 1文字であるとします.

sh をシェルとして使う
sh を起動してみる.
% sh[Enter]
$        ←プロンプトが変わる
今は csh の中で sh が起動している.sh もシェルなので,コマンドを実行することができる.
$ date[Enter]
2009年  7月  2日 木曜日 13:20:00 JST

このように,sh もコマンドを実行するための標準的なシェルとして使用されます.特に Linux では,sh と互換性のある bash がログインシェル(ログイン時やターミナルのウィンドウを開いたときに実行されるシェル)としてよく使われます.sh はコマンドを実行する点では csh と同じですが,複雑なことをしようとしたときの書き方(文法)が異なります.sh (bash) の詳細は,man bash で調べてください.

1.4 sh のシェル変数と環境変数

sh はシェル変数や環境変数の取り扱い方が csh とは異なります.sh ではシェル変数に値を代入する際に set コマンドを使用しません.ただし,シェル変数を消去するには unset コマンドを使います.

シェル変数 (sh)
変数 name に ichiro を代入してみる.変数 name は自動的に作られる.sh では変数への値の格納に set コマンドを用いない.また "=" の前後にはスペースを入れない
$ my="My name is"[Enter]
echo コマンドを使って,name の内容を見てみる.変数の値を参照する(取り出す)には,変数名に "$" を付ける.
$ echo $my[Enter]
My name is
read コマンドを使えば,標準入力から入力したデータを変数に格納できる.
$ read name[Enter]
Kentaro[Enter]  ←データ入力待ちになるので,何か入れて改行
$ echo $my $name[Enter]
My name is Kentaro  ←さっき入れたものが入っている
1回で複数の変数への代入ができる.expr は引数を数式とみなして計算するコマンド.
$ v1=10 v2=20[Enter]
$ echo $v1 $v2[Enter]
10 20
$ expr $v1 + $v2[Enter]
30
set コマンドでシェル変数を全部表示する.
$ set[Enter]
シェル変数を消去するには unset コマンドを使う.csh とは異なり,未定義の変数を参照してもエラーメッセージは出ない.
$ unset v1 v2 v3[Enter]
$ echo $v1[Enter]
                ←何も出力されない

sh の環境変数はシェル変数と密接に関連しています.環境変数を設定するには,一旦シェル変数を設定してから,それを環境に組み込みます.これには export コマンドを使います.また環境変数の解除には,シェル変数同様 unset コマンド,環境変数の一覧を表示するには env コマンドを使います.

環境変数 (sh)
HENSU という変数を表示するだけの csh スクリプト b を作る.もちろん emacs を使ってもよい
$ cat > b[Enter]
#!/bin/csh -f[Enter]
echo $HENSU[Enter]
Ctrl-D
b を実行可能にする.
$ chmod +x b[Enter]
b を実行してみる.多分エラーになる.
$ ./b[Enter]
HENSU: 変数を定義していません.
シェル変数 HENSU に値を与えてもエラーになる.
$ HENSU=10[Enter]
$ ./b[Enter]
HENSU: 変数を定義していません.
export コマンドを使って,シェル変数 HENSU を環境に組み込む.今度はうまくいく.
$ export HENSU[Enter]
$ ./b[Enter]
10
コマンドの左にシェル変数への代入を置くと,そのコマンドに対してだけ一時的に環境変数となります.
$ HENSU=20 ./b[Enter]
20  ←b では環境変数 HENSU に 20 が入っている
$ echo $HENSU[Enter]
10  ←もとのシェルでは HENSU は 10 のまま

1.5 引用符

シェルや他のプログラミング言語において,データや画面表示に使う単語や文章のような「文字がならんだデータ」のことを文字列と呼びます.文字列は,それが空白などを含んでいてもひとかたまりのデータとして扱えるように,引用符でくくります.sh や csh では,引用符によって動作が違います.

引用符による動作の違い
'~'
'~'(シングルクォーテーションマーク,シングルクォート)ではさんだものは,スペースなどを含めて単一の文字列として取り扱われます.
"~"
"~"(ダブルクォーテーションマーク,ダブルクォート)ではさんだものは,スペースなどを含めて単一の文字列として取り扱われます.ただし,変数(シェル変数,環境変数)はその内容に置き換わります.また次の `~`(バッククォーテーションマーク)による置き換えも行われます.
`~`
`~`(バッククォーテーションマーク,バッククォート)は上の2つとは異なり,この間にはさんだものをコマンドとして実行します.そして,その標準出力への出力を,この場所に書かれた文字列として取り扱います.
引用符
echo コマンドで *(アスタリスク)を出力しようと思う.しかし,* はワイルドカードなので,ファイル名の一覧が出力されてしまう.
$ echo *[Enter]
(ファイル名の一覧が出力される)
\ を使えば * の効果を打ち消すことができる.
$ echo \*[Enter]
*
'~' や "~" ではさんでも * を出力できる.
$ echo '*'[Enter]
*
$ echo "*"[Enter]
*
echo コマンドで $HOME を出力してみる.HOME はホームディレクトリの場所を格納している環境変数.
$ echo $HOME[Enter]
/home/s3/s095000  (ホームディレクトリのパス名)
\ を使って $ の効果を打ち消すことができる.
$ echo \$HOME[Enter]
$HOME
しかし,"~" ではさんでも $ の効果は生きている.
$ echo "$HOME"[Enter]
/home/s3/s095000
'~' では $ がそのまま出力される.
$ echo '$HOME'[Enter]
$HOME
`~` にはさんだものは,コマンドとして実行される.右の例は,date コマンドの出力をシェル変数 d に格納する.
$ d=`date`[Enter]
$ echo $d[Enter]
2009年  7月  2日 木曜日 13:25:00 JST

それでは,sample.sh を次のように変更(太字の部分を追加)してください.

#!/bin/sh
echo "今日は"
date
echo "です."
echo "あなたは平成何年生まれですか?"
read year
echo "あなたは何月生まれですか?"
read month
echo "あなたの誕生月のカレンダーです."
cal $month `expr $year + 1988`

変更が完了したら C-x C-s で sample.sh を保存し,ターミナルのウィンドウでそれを実行してみてください.

sample.sh を実行する
$ ./sample.sh[Enter]

引用符や変数は組み合わせて使用することができます.sample.sh を,次のように変更してみてください.最初に表示している3行を,1行にまとめます.date コマンドの引数について知りたければ,man date で調べてください.

#!/bin/sh
d=`date +'%m月%d日'`
echo "今日は$dです."
echo "あなたは平成何年生まれですか?"
read year
echo "あなたは何月生まれですか?"
read month
echo "あなたの誕生月のカレンダーです."
cal $month `expr $year + 1988`

C-x C-s で sample.sh を保存し,ターミナルのウィンドウで sample.sh を実行してみてください.

sample.sh を実行する
% ./sample.sh[Enter]

date コマンドは引数で出力する日付と時間の書式を制御できます.詳しくは man date か jman date を見てください.

1.6 プロンプトを出す

この sample.sh を実行すると,「あなたは平成何年生まれですか?」という質問が表示された後,次の行に改行してデータの入力待ちになります.「あなたは平成何年生まれですか?」という表示は,データの入力を促すプロンプト(入力促進符号)なので,ここで改行されないほうが見やすいかもしれません.echo コマンドに -n というオプションを付ければ,この改行を抑止できます.

echo コマンドの -n オプション
コマンドを ;(セミコロン)で区切れば,1行に複数のコマンドを書くことができる.この例では,ひとつの echo コマンドごとに改行が出力される.
$ echo "This is "; echo "a pen."[Enter]
This is 
a pen.            ←改行が入る
echo コマンドに -n オプションを付ければ,この改行を抑止できる.
$ echo -n "This is "; echo "a pen."[Enter]
This is a pen.    ←改行が入らない
echo -n と read の組み合わせ
read コマンドがデータ入力を待っているときは,プロンプトは表示されない.そこで,echo コマンドを使って自分でプロンプトを出してやることにする.しかし,これではプロンプト(ここでは "x=")の後に改行が出力されてしまい,read による入力データ待ちと分離してしまう.
$ echo "x="; read x[Enter]
x=          ←echo コマンドを使って出したプロンプト
10[Enter]  ←データ入力待ちになるので何か入れて改行
echo コマンドに -n オプションを付ければ改行が出力されないため,プロンプトの直後で入力データを待つことができる.
$ echo -n "x="; read x[Enter]
x=10[Enter] ←プロンプトのところでデータ入力を待つ

それでは,sample.sh の中の2つの echo コマンドに,-n オプションを追加してください.

#!/bin/sh
d=`date +'%m月%d日'`
echo "今日は$dです."
echo -n "あなたは平成何年生まれですか?"
read year
echo -n "あなたは何月生まれですか?"
read month
echo "あなたの誕生月のカレンダーです."
cal $month `expr $year + 1988`

追加できたら C-x C-s で sample.sh を保存し,ターミナルのウィンドウで sample.sh を実行してみてください.

sample.sh を実行する
$ ./sample.sh[Enter]

1.7 sh の終了

実は sh は csh より古く,Unix の初期のログインシェルとして使われていました.現在ではログインシェルとして使用するよりも,プログラミング言語としてよく使われます.ただし bash(皆さんが今使っている sh)は,Linux における標準的なログインシェルとしても使われています.

sh を終了する
sh を終了する.sh の場合も Ctrl-Dexit コマンドで終了できる.
$ exit[Enter]
%    ←csh のプロンプトに戻る

2.数を数える sh スクリプトの作成

2.1 カウンタスクリプトの動作

次に,sh スクリプトで数を数えるコマンド(カウンタ)を counter.sh というファイル名で作ってみましょう.カウンタとは,下の例のように,実行するたびに出力する数値が1ずつ増えていくコマンドです(以下はあくまでも例ですから,今はまだ実行できません)

カウンタの動作
% ./counter.sh[Enter]
1
% ./counter.sh[Enter]
2
% ./counter.sh[Enter]
3 ←実行するたびに数が増えていく

とりあえず emacs を使って counter.sh というファイルを作成します.これは sh スクリプトですから,1行目は #!/bin/sh になることに注意してください.それ以降のシェルスクリプトの内容は,以下の文章を読んで,自分で考えてください.

counter.sh は数値(カウント)を出力するとすぐに終了してしまいますから,counter.sh は何らかの方法で前回出力した数値(カウント)が何だったか覚えておく必要があります.ここでは,それをファイルに記録しておくことにします.この段取りは次のようになります.

  1. 前回出力したカウントをファイルから取り出す
  2. このカウントに1を加える
  3. この結果を出力する
  4. この結果をファイルに記録しておく

カウントを記録しておくファイルのファイル名は count.dat とします.変数にデータを格納するには read コマンドを使用しますが,ここではそのデータを count.dat から取り出す必要があります.したがって,このファイルの中からデータを取り出してシェル変数に格納するには,read コマンドの標準入力をリダイレクトして,count.dat からデータを取り出すようにします.

ファイルから変数にデータを格納する
read count < count.dat

ただし,最初に counter.sh を実行した時には前回のカウントは調べられませんから,その場合はカウントを 0 としておく必要があります.最初に counter.sh を実行したときには count.dat は存在しませんから,このファイルが見つからなければ counter.sh を初めて動かしたのだと判断できます.あるファイルが存在するかどうかを調べるには,test コマンドを使います.

test コマンドと終了ステータス
コマンドはその実行を終えると,終了ステータスをシェルに報告する.直前に実行したコマンドの終了ステータスは $? という特殊な変数で取り出すことができる.
$ ls[Enter]  ←何かコマンドを実行してみる
(ファイル名の一覧が出力される)
$ echo $?[Enter]
0 ←終了ステータス 0(ls の実行成功)
終了ステータスは整数値であり,0 が真(コマンドは正常に実行できた),非 0 が偽(コマンドの実行に失敗した)を表す.
$ ls xyz[Enter]  ←引数に存在しないファイル名を指定
ls: xyz: そのようなファイルやディレクトリはありません
$ echo $?[Enter]
1 ←終了ステータス 1(ls の実行失敗)
test コマンドは引数に指定したテストを実行して,結果を終了ステータスでシェルに報告する.tesf -f file だと,file というファイルが存在すれば,終了ステータスは真 (0) になる.
$ test -f xyz[Enter]
$ echo $?[Enter]
1 ←test コマンドは何も言わないが終了ステータスは 1
$ test -f sample.sh[Enter]
$ echo $?[Enter]
0 ←sample.sh というファイルはさっき作ったのであるはず

2.2 if(条件判断)による実行制御

test コマンドはファイルの有無のほかにも,いろんなことを調べることができます.詳しくは man test で調べてください.これと if という制御文を組み合わせれば,終了ステータスをもとにコマンドの実行順序を制御(分岐)できます.

if と終了ステータス
if コマンド
then
    (上のコマンドの実行が成功したときに
     この部分のコマンドが実行される)
else
    (上のコマンドの実行が失敗したときに
     この部分のコマンドが実行される)
fi

すなわち,「もし(if),コマンドの実行が成功した,ならば(then) 前半部分のコマンドを実行する,さもなければ(else)後半部分のコマンドを実行する」という実行順序の制御を行います.ここでは count.dat というファイルが存在すれば,それからデータを取り出し,さもなければ(ファイルが存在しなければ)カウントを 0 にするという処理を行いますから,これは次のようなスクリプトになります.

count.dat があるときだけデータを取り出す
if test -f count.dat (もし count.dat が存在すれば)
then (そのときは)
    read count < count.dat (count.dat から変数 count に値を読み込み)
else (そうでなければ)
    count=0 (変数 count を 0 にする)
fi

これでようやく「前回のカウント」をシェル変数 count に取り出すことができました.それでは,これに1を加えましょう.シェル変数 count の値は $count として参照できます.expr コマンドを使って,これに 1 を加えます.これは expr $count + 1 でできます.この出力を再び count に格納します.それには `~` を使います.

シェル変数 count に 1 を加える
count=`expr $count + 1`

あとは,このシェル変数 count の値を標準出力に出力し,さらにファイル count.dat にも保存すれば完了です.コマンドの出力をファイルに保存するには,標準出力をリダイレクトします.

シェル変数の値を標準出力とファイルに出力する
echo $count
echo $count > count.dat

2.3 シェルスクリプトの終了

すべての処理が終わったら,sh スクリプトを終了します.sh スクリプトの終了にも exit コマンドを使いますが,その引数にこのスクリプトの終了ステータスを指定します.sh スクリプトが正しく仕事を完了して終了するなら,通常ここに 0 を指定します.終了ステータスを指定しなければ,sh スクリプト内で最後に実行したコマンドの終了ステータスが,スクリプトの終了ステータスとなります.

sh スクリプトを終了する
exit 0

以上の内容の sh スクリプト counter.sh を作成し(1行目に #!/bin/sh を入れるのを忘れないように),それに実行許可を与えてから実際に実行してみてください.ちゃんと実行回数を数えることができるでしょうか?

カウンタの実行
% ./counter.sh[Enter]
1
% ./counter.sh[Enter]
2
% ./counter.sh[Enter]
3
expr: non-numeric argument (数値でない引数)と表示されるとき
データファイルの count.dat の内容に数値でないものが入っていたり,空だったりすると,expr コマンドがエラーになって,"expr: non-numeric argument (数値でない引数)" と表示されます.このときは count.dat を削除してください (rm count.dat).

3.アクセスカウンタの作成

3.1 SSI (Server Side Include)

Webページ中にカウンタを組み込むめば,Web ページがどのくらいの人に見てもらえたのか数えることができます.このようなカウンタはアクセスカウンタと呼ばれたりします.SSI (Server Side Include) という機能を使えば Web ページ中にコマンドの出力を埋め込むことができるので,この機能と前節で作成したカウンタのスクリプト counter.sh を使って,簡単なアクセスカウンタを作ってみましょう.

注意
SSI は間違った使い方をするとサーバにトラブルを起こしたり,大きなセキュリティホール(Webページの改ざんや不正侵入などを可能にしてしまうような不備)を作る場合があるため,スクリプトの作成は慎重に行う必要があります.

counter.sh をホームページ公開用のディレクトリ public_html の下にコピーしてください.

% cp counter.sh ~/public_html[Enter]

次に,public_html に下のような HTML ファイルを作成してください. ファイル名は counter.shtml としてください.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  <html>

  <head>
    <title>counter test</title>
  </head>

  <body>

    あなたは <!--#exec cmd="./counter.sh"--> 番目の訪問者です.

  </body>

</html>

こうすると,http://com.center.wakayama-u.ac.jp/~ユーザ名/counter.shtml にアクセスするたびに counter.sh が実行されて,その出力が <!--...--> の部分と置き換わります.counter.sh の出力が 1234 なら,この部分は次のようになります.

あなたは 1234 番目の訪問者です.

参考までに,別のファイル(下の例では file.txt)を埋め込むときには次のようにします.

××さんは次のように言いました「<!--#include file="file.txt"-->

3.2 グラフィカル?なアクセスカウンタ

グラフィカルなカウンタを作るためには,PerlGD (日本語訳はここ)というモジュールを使ったりすると便利なのですが,ここでは counter.sh の出力を工夫してみます.まず,次のような数字の画像を counter.shtml と同じディレクトリ内に用意したとします.

0 1 2 3 4
0.png 1.png 2.png 3.png 4.png
5 6 7 8 9
5.png 6.png 7.png 8.png 9.png

このとき,HTML で次のように表現すれば, という表示が得られます.

<img src="1.png"><img src="2.png"><img src="3.png"><img src="4.png">

つまり,counter.sh の 1234 といった出力を上のように変換すれば,グラフィカルなカウンタが実現できます.このような文字列の置き換えを行うには,sed というコマンドを使うと便利です.sed は正規表現を使って入力データを加工するフィルタコマンドです.

% echo 1234 | sed 's/./<img src="&.png">/g'[Enter]
<img src="1.png"><img src="2.png"><img src="3.png"><img src="4.png">

4.その他の実行制御

sh には if 以外に繰り返しを行う for や while,複数の条件に分岐できる case などの実行制御機能があります.

for(個々の引数について繰り返し)
for x in this is a pen.
do
    echo $x
done

forin の右側に書かれたものを一つ一つシェル変数 x に格納し,そのたびに dodone の間を実行します.上の例では x に this を入れた後 echo $x を実行し,次に is を格納して echo $x を実行するという具合に,this,is,a,pen. の4つに対してそれぞれ echo $x が実行されます.ここにワイルドカードを指定すれば,カレントディレクトリにある一つ一つのファイルに対して処理を行うことができます.

while(条件が満たされている間繰り返し)
while read x
do
    echo $x
done

while は while の右側に書かれたコマンドの終了ステータスがの間,dodone の間を繰り返し実行します.上の例では read が成功している間,すなわちデータの入力が行われている間,echo $x が実行されます.データの入力が終了した(ファイルの終わりに達した)場合などに終了ステータスはになりますから,繰り返しは終了します.

case(パターンにあわせて処理を選択)
case $x in
a)
    echo "$x は a というファイル"
    ;;
b*)
    echo "$x は b で始まるファイル"
    ;;
*)
    echo "$x はその他のファイル"
    ;;
esac

casein との間に書かれたものと,")" のところに書かれた文字列パターン(case ラベル)との間でパターンマッチング(文字列パターンの照合)を行います.この文字列パターンにもワイルドカードが使用できます.

for file in *
do
    case $file in
    *.bak|*~)
        echo "$file はバックアップファイルです."
        rm -i $file
        ;;
    *.txt)
        echo "$file はテキストファイルです."
        echo -n "中身を見ますか?"
        read answer
        case $answer in
        y*)
            less $file
        esac
        ;;
    *)
    echo "$file はその他のファイルです."
    ;;
esac

上の例は,カレントディレクトリにあるすべてのファイル (*) について,ファイル名の最後が ".bak" または (|) "~" のファイルは「バックアップファイル」であるとみなして削除します.また,ファイル名の最後が ".txt" のファイルは「テキストファイル」とみなして,ページャ (less) を使って中身を見るかどうかたずねます.ここで yes など y で始まる文字を入力すれば,中身を見ます.

課題

以下の内容を一つのメールにまとめて tokoi@sys.wakayama-u.ac.jp まで送ってください.Subject:(件名)は kadai12 にしてください.締め切りは次週の水曜日とします.

(1)「1.5 引用符」のところで使った「cal $month `expr $year + 1988`」というコマンド行の動作を説明しなさい.
(2)「2.数を数える sh スクリプトの作成」において作成した counter.sh の内容をメールの本文にコピーアンドペーストしてください.
(3)SSI を使って counter.sh の出力を埋め込んで表示する Web ページを counter.shtml というファイル名で作成してください.そして,そのページをhttp://com.center.wakayama-u.ac.jp/~ユーザ名/counter.shtml からアクセスして,アクセス回数が表示されていることを確認してください.また,「再読み込み」ボタンをクリックして,アクセス回数が増加することを確認してください.以上が完了したら報告してください.
(4)時間があれば gimp 等を用いてカウンタ用の文字画像を作成し,counter.sh をグラフィカルなアクセスカウンタに改造してください.文字画像として「3.3 グラフィカル?なアクセスカウンタ」で使っている 0.png ~ 9.png をそのまま使っても構いません.
注意
counter.sh はカウント数のデータファイル (count.dat) の更新時に排他制御をしていないので,複数の人が同時にアクセスしたときなどにカウントが狂ってしまいます.そういう状況が起こらなければ正常にカウントしてくれますが,あまり実用にはなりません.

排他制御

複数の人が同時に counter.shtml にアクセスすると,複数の couter.sh が同時に実行されます.そうすると,複数の counter.sh が同時に count.dat にデータを出力したり,ある counter.sh が count.dat にデータを出力している最中に別の counter.sh が count.dat からデータを入力してしまうといったことが起こります.この場合,count.dat の内容が変になったり,正常なデータの読み出しが行えなかったりします.

このようなときには,一つの counter.sh だけがデータファイル count.dat を更新し,他は更新が終わるまで待つという処理を行う必要があります.このような処理を排他制御と言います.

lockfile というコマンドを用いれば,この排他制御を簡単に実現できます.このコマンドは次のような動作をします.詳しくは man lockfile を見てください.

これを counter.sh に応用するには,たとえば次のようにします.

#!/bin/sh
lockfile="counter.lock"
if lockfile -3 -r 3 -l 10 $lockfile
then

(もとの counter.sh の中身)

fi

rm -f $lockfile

exit 0

ここで lockfile コマンドのオプション -3 は,ロックファイル (counter.lock) が存在すれば3秒待つことを表します.これを3回繰り返し (-r 3),その間にロックファイルが無くなればロックファイルを作成して成功とします.もしロックファイルが無くならなければ失敗とします.ただし,存在するロックファイルが作成後10秒以上経過 (-l 10) していたら,そのロックファイルを削除して新たにロックファイルを作成して成功とします.最後のオプションは,counter.sh が異常終了してロックファイルを消さなかった場合などに有効です.