本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはJapanize やText::MicroTemplateなどで有名な奥一穂さんで、テーマはUNIXプログラミングです。
はじめに
「正しいプログラム」を書くために必要な知識とはなんでしょうか。テストが正しく実行されることは、正しいプログラムであるための必要条件に過ぎません。プログラミングにおいてはプログラミング言語だけでなく、OSの動作やデータベース、ネットワークプロトコルなど、さまざまな知識が必要になります。
PerlはもともとUNIX系のOS(LinuxやFreeBSD、Mac OS XなどのOSを本稿では以下UNIXと総称します)の上で処理を行うためのスクリプト言語として開発され、今日でもPerlプログラムの多くはUNIX上で実行されています。しかし、「 Perlでプログラミング」と言った場合はともすればPerlの言語機能(あるいはモジュール)の話になりがちな一方、「 UNIXのプログラミング」の議論においてはC言語で書くことが前提となることが多く、「 PerlでUNIXプログラミング」する方法について論ぜられることはあまりありません。これは、C言語がUNIXを扱うための「システムプログラミング言語」として発達してきた一方、Perlがレポート処理やWebアプリケーションなどを記述するためのスクリプト言語として育ってきた歴史を考えれば無理からぬところではあります。
しかしアプリケーションを記述する場合でも、押さえておくべきUNIXプログラミングの知識が何点かあります。そこで本稿では、正しいプログラムを書くうえでは避けて通れないUNIXのエラー処理から順に、PerlでUNIXプログラミングをするうえでの勘所をサンプルコードとともに紹介していきます。
エラー制御
プログラムを書く際によく見落とされているのが、エラー制御です。めったに発生しないエラーであればあるほど、テストでも試験運用でも問題が発生せず、Webサービスなら本番環境に投入してから(あるいはソフトウェア製品なら納品・公開後に)障害対応をするはめになります。本節では、「 ディレクトリが存在しなければ作成」という処理を例に、正しいエラー処理のしかたを説明していきます。
mkdirの正しい呼び方とは?
「データを共有するディレクトリが存在しなければ作成」という処理を書いたことのある人は多いと思います。では、どのように書くのが正しいでしょう。
if (! -d $dir) {
mkdir $dir
or die "failed to create dir:$dir:$!";
}
これでよいでしょうか。いえ、違います。これだと、-d演算子でディレクトリの不存在を確認したあと、mkdirを呼ぶまでの間にほかのプロセスがディレクトリを作成してしまう可能性があります。なので、たとえばCならば次のように、mkdirを呼び出してエラーが返ってきた場合には原因を確認する、というのが「正しい」コードになります。
if (mkdir(dir) != 0 && errno != EEXIST) {
fprintf(stderr, "failed to create directory:%s:%s \n",
dir, strerror(errno));
exit(1);
}
では、同様のコードをPerlで書く場合はどうすればよいのでしょう。
unless (mkdir $dir or $! =~ /exists/) {
die "failed to create dir:$dir:$!";
}
これでよいでしょうか。いえ、やっぱり違います。エラー文字列はロケールによって変化するので、たとえばLANGがja_JP.utf8の場合は「ファイルがすでに存在」のような文字列になります。ですから、$!
を正規表現で確認する、という手段は使えません。
正解は、次のコードです。
use Errno ();
unless (mkdir $dir or $! == Errno::EEXIST) {
die "failed to create dir:$dir:$!";
}
このコードを理解するうえで必要になる知識は、まず、$!
がdualvar、つまり、文字列として評価した場合は文字列を、数値として評価した場合は数値を返す変数であるということです。このコードでは!=演算子を適用しているので、$!
は数値として評価されます。LinuxやMac OS Xでは、すでに同名のディレクトリが存在したためにmkdirが失敗すると「17」という値が返ってきます。数値であればロケールによって変化することはありませんから、エラーの判定に適しています。
ただ、この場合に返されるエラー番号が17という値かどうかはOSによって異なります。そこで、Errnoモジュールに定義されている定数Errno::EEXISTと比較することで、OSに依存しないエラー処理が完成することになります。
Errnoの調べ方
では、各関数がどのようなエラーを返す可能性があるか、どうやって調べればよいでしょうか。残念なことに、perldoc -f mkdir
しても答えは書いてありません。各関数が返
す可能性のあるエラーの一覧を知るには、OSのmanページを参照する必要があります。たとえば筆者のLinux環境のコンソールからman 2 mkdir
と入力して実行すると、図1 のようにERRORSセクションに返ってくる可能性のあるエラーコード一覧が示され、その中にEEXISTが含まれていることが確認できます。
OSに依存しないプログラムを書く場合は、各種UNIXの共通仕様を定めたSingle UNIX Specifi cationのドキュメント を参照するとよいでしょう。
図1 man 2 mkdirの実行例
MKDIR(2) Linux Programmer's Manual MKDIR(2)
NAME
mkdir - create a directory
...
ERRORS
EACCES The parent directory does not allow write permission to the
process, or one of the directories in pathname did not allow
search permission. (See also path_resolution(7).)
EEXIST pathname already exists (not necessarily as a directory). This
includes the case where pathname is a symbolic link, dangling or
not.
まとめ
mkdirの例で見たように、複数のプログラムが同時に動く結果、競合が発生する可能性があるファイル操作においては、実際の操作を行う関数を呼び出して、そのあとにErrnoをチェックするという操作をすることで、結果的に正しい状態になっていることを保証するというプログラムパターンが一般的です。symlink(シンボリックリンクを作成) 、rename(ファイルをリネーム) 、unlink(ファイルを削除)といった関数は、いずれもこのような操作が可能になっています。
また、ファイル操作以外でも、ネットワーク通信を行うプログラムなどではエラー処理が重要になってきます。すべてのケースでエラー処理を書くべきだとは言いませんが、プログラムの外部に対して恒久的な影響を与えるような操作については、異常時の動作についても配慮しておいたほうがよいでしょう。