written: 1999-12-08 .. 2019-03-17
『小数の四捨五入・切り捨てに関する都市伝説を斬る!』『5.10-20』『モジュール』『flock() について』『Unicode/UTF-8 について』『まだまだ!『プログラミングPerl 改訂版』正誤表』『汚染モードについて』『rand(), srand() について』
5.10-20 における変更点をざっとまとめておきます。(参考:perldelta 5.20/18/16/14/12/10)
use strict;
のデフォルト化Perl で「モジュール」と呼んでみるものは要するに C 言語その他の言語で言うところの「ライブラリ」のことで、プログラムのうちのメインルーチンからサブルーチンとなる関数群を分離・独立させて別個の「ファイル」にしたもの、というイメージ。これ(モジュールと呼ぼうがライブラリと呼ぼうがその人の勝手)によって、その関数群は、再利用が可能となるわけだ。
作成したモジュールを各マシンの Perl 環境にインストールできるようにするための配布用パッケージ化するためには、Module::Build を使う。昔は、Module::Build を使う環境がまだあまり整っておらず、C 言語的な作法を色濃く反映した Makefile を使うやり方が一般的だったが、今では Build が使い易い状況になった。インストール作業は次のような手順で行う:
Build.PL Build Build test Build install Build realclean
Build.PL(perl スクリプト)を実行することによって、インストールに関する設定情報が記録された Build というファイルを作成する。この Build は(本来は C 言語用のツールである)make コマンドによって解釈されてビルド、テスト、インストール、クリーンアップ等を可能にする。
昔、Module::Build 環境があまり整っていなかった頃は、make コマンドを自分で別途用意する必要があり、(UNIX 系ならデフォルトなのだが)Windows の場合は必ずしも make コマンドは使えないので、それだけでも敷居が高かった。ところが、今では ActivePerl をインストールした後、(cpan コマンドを使って)CPANを一回起動するだけで、自動で mingw をインストールして make 関係も整備してくれるので、Module::Build を使うために色々と自分で苦労する必要はなくなった。
インストーラ作成は、Build ファイルを作成した状況で Build dist を行う:
Build.PL Build dist
(2004-02-06 .. 2005-10-27)
flock() は perl 固有の関数というよりも、本来的には UNIX のシステムコールを利用するもの(システムが flock を実装していない場合にはエミュレーションする)なので、OS の flock のマニュアルを参照した方が話が早いかもしれない。
LOCK_EX(排他ロック)についてはまさしくファイル・ロック機能そのものだと思うので、疑問の生じる余地は少ないと思うが、では、LOCK_SH(共有ロック)とは何のためにあるのか、どのようにして使えばいいのだろうか? man によると「一つのファイルに共有ロックと排他ロックを同時に設定することはできない」とあるから、排他ロックは他の排他ロックと共有ロックに対して排他的であり、共有ロックは他の共有ロックに対して共有的で排他ロックに対しては(排他ロック側の排他性による結果ではあるが)排他的である、と考えることができる。
この LOCK_SH の特性については、実験用の CGI プログラムを作成して確認することができた。この実験プログラムでは、アクセスカウンタを 1 回のファイルアクセスにつき 1 ずつ、累計 25000 まで加算を行う。また、1000 回加算ごとに、データファイルに記録されたカウント数を読み出して表示する。このアクセスカウンタをフレームで同時に 4 つ起動することによって、お互いの動作に影響があるかどうかが確認できる(IE で動作確認。Windows の設定によっては HTTP 同時アクセス数の制限を上げる必要があるかもしれない。また Firefox の場合、ブラウザ側でフレームの内容を順番に読み込み、同時に読み込んでくれないので、ちゃんと動作しない場合がある)。いずれかのフレームの読み出しカウント数が最終的に 100000 になれば、排他ロックが有効であると言える。(※この実験プログラムに関しては、がまさんの実験プログラムのアイデアを参考にさせていただきました。)
共有ロックはそれ自身排他的ではない点はロックなしの場合と同じだが、他に排他ロックを必要とするプログラムが存在する場合、ちゃんと共有ロックしないと、相手が排他ロックしたがっていてもお構いなしに同時アクセスを行ってしまうのがわかる。
ところで、共有ロックは共有ロック同士でファイルを共有する一方、排他ロックを排除するから、共有ロックを使用するプロセスが頻繁に発生するようなプログラムの場合、共有ロックの後からやってきた排他ロックは順番待ちで待たされる一方、さらにその後からきた共有ロックはその順番待ち中の排他ロックを追い抜かしてファイルを新たに共有ロックすることになるのではないかと思うので、その結果排他ロックの順番が繰り下がっていって順番が巡るのが遅延する可能性がある。こういう場合は、共有ロックで用が足りるプロセスであっても、排他ロックとして動作させるようにプログラムするべきケースかもしれない。共有ロックを使用するプロセスがたまにしか発生しないプログラムであれば、こういった問題は気にする必要がない。
しかし、よく考えてみると、共有ロックを使用するプロセスが低頻度でしか発生しないようなプログラムであれば、むしろそれをわざわざ共有ロックせずとも、すべからく排他ロックしてしまっても全体の処理効率からいえば大差ないことになるのである。一方、共有プロセスで済むようなプロセスが頻繁に発生するプログラムの場合、それらをすべて排他ロックとして動作させた場合は、それらのプロセス同士が排除しあってプログラム全体の処理の遅延を招くことになるが、共有プロセスとして動作させればそれら同士で競合することなく、スムーズに処理を行えるようになる。この場合、一方の比較的低頻度で発生することになる排他ロックを要するプロセスにとっては、“追い越し”による多少の理不尽な順番待ちを強いられることになるかもしれないが、プログラム全体の処理効率でいえば、結局はその方が合理的だということになるのかもしれない。
ロックのモードは数字で指定するのが普通だが、Fcntl モジュールからシンボルを導入すれば、LOCK_SH, LOCK_EX, LOCK_NB, LOCK_UN をそのまま用いることが可能である。シンボル導入には、use Fcntl ':flock';
を使う。LOCK_NB(ブロックせず)を指定するときには、例えば LOCK_EX|LOCK_NB
という風に指定する。
LOCK_UN(ロック解除)は出力バッファの問題で明示的に使わずにファイル・クローズによって暗黙的にロック解除するべきとされていたが、これは旧来の Perl の問題であり、新しい Perl(5.4 以降)では気にする必要がなくなった。
flock は「1) ファイルをロックしてから、2) ファイルをオープンする」のではなく「1) ファイルをオープンしてから、2) ファイルをロックする」という仕組みになっているため、対象となるデータファイルを直接ロックすることによって排他制御しようとする場合は、オープンしてから次にそのファイルハンドルに対してロックを行う間隙に、他のプロセスによってファイルが変更を受ける可能性が残されているので、そのことに対する配慮が必要だと言われる。例えば、追加書込でオープン(open FH, '>>filename'
)したような場合は、この間隙に他のプロセスによってファイルの末尾にさらに新しいデータが追加されている可能性がある。そこで、ファイルハンドルに対するロック操作後に、seek FH, 0, 2
によってファイル・ポインタの位置を最後尾に移動してから、追加書込を行う必要があると、『ラクダ本』の解説(参考:perldoc.perl.org)でも述べられている。しかし、このケースに関しては、筆者の環境(プロバイダの Web サーバ)においては、考慮する必要はないようである。どうやら Linux の場合、ファイルシステムの側で書込に対してロック機構が備わっており(参考:japan.linux.com)、元から同時多重書込が行われないようになっているので、特に Perl プログラム側で対処する必要はないらしいのである。実際に、追加書込による問題を発生させるために実験用プログラムを作成しようと四苦八苦したが、どうやっても問題を発生させることができなかった。
また、ロックするよりも先にオープンするので、ロックするつもりかどうかにかかわらず、書込オープン(open FH, '>filename'
)してしまった場合は、print せずとも、オープンと同時にファイルの中身が空にされてしまう。こうなってしまうと、他の共有ロックのプロセスが誤って空の状態の時に読込を行ってしまう可能性を生じる。つまり、排他ロックしながら書込を行いたい場合は、書込オープンは使ってはならないことになる(参考:とほほの perl 入門)。この場合、読み書き両用オープン(open FH, '+<filename'
)を使って、とりあえず読込用としてオープン(この場合は、ファイルの中身が空にされない。同じ読み書き両用オープンでも open FH, '+>filename'
は最初に書込オープンされてしまうので×)する必要がある。その上で、ロック後に、書込オープンと同様の初期状態にするための作業を truncate FH, 0
(ファイルを空に)と seek FH, 0, 0
(ファイル・ポインタを先頭に巻き戻す)によって行ってから、print によってデータの書込を行うことになる。
対象となるデータファイルを直接ロックするやり方に対して、データの書込対象としては直接使用しない別個のロック専用のファイルを作り、ロック専用ファイルをオープンかつロックしたプロセスのみがデータ書込対象となるデータファイルにアクセス可能とするようなアルゴリズムにしておけば、「1) ファイルをロックしてから、2) ファイルをオープンする」というユーザの求める順番の動作が実現できることになり使い勝手がいいように思われる。データファイルのアクセスが終了後、ロック専用ファイルに対するロックを解除することで、他のプロセスに対するアクセスが解放される。
(2004-01-04~2004-01-16)
文字コードの問題は、ともかくややこしい。Unicode に限らず、メールの 7bit コードの問題など、よく悩まされる。本来、物理的には二進数の 0 / 1 で表現されるビット【bit】の羅列からなるバイナリコードを、論理的にどういう規則でビットを束ねて認識するかということに端を発している。
8bit をひとくくりとして束ねたものをバイト【byte】と言う。これは要するに、欧米のアルファベット文字圏においては、8bit = 256 種類の文字をマップ(数字を文字の背番号として対応付け)すれば、論理的な文字表現として(記号や制御文字を含めても)事足るからだ。例えば、フロッピーディスクなどの容量はバイト単位で言い表されるが、 1 メガバイトのフロッピーディスクであれば、そのまま文字数 1 メガ(100 万)文字の欧文テキストを格納できることを意味する。抽象的な単位ではなくて、極めて具体的・直観的な単位なのである。
だが残念なことに、日本語のテキストの場合は、文字数とバイト数が一致することはないので、直観的な単位ではなく、専門知識のない人々にとっては敷居を高くする一つの専門用語的語彙となっている。──さらに、英語を使うアメリカ人にとっては、フランス語やドイツ語等の発音記号は不要だから、8bit どころか、7bit でも十分なので、7bit = 128 種類の文字をマップする US-ASCII コードで事足りる。そこで US-ASCII をベースにしてメールの規格を作ってしまったものだから、我々のような日本語ユーザどころか、同じアルファベット文字圏である英語以外の他のヨーロッパ語ユーザにすら不便な思いを強いることになったという過去がある。
日本語の文字は、8bit のバイトコードを論理的な文字コードとして認識し、その上で、バイトコードを組合せて符号化して、日本語の文字のマップを示すというようなやり方で対応することが可能である。1byte = 8bit = 256 種類だから、1byte の US-ASCII 文字を 2 個組合せれば、2byte = 16bit = 65536 通りの文字をマップすることができる。この 2byte の文字の頭に、それが日本語の文字(2byte で背番号を表現する文字である旨)の開始である印となる US-ASCII の文字 1byte を付けた、計 3byte が、日本語の文字 1 文字を示す UTF-8 コードとなる(この説明はかなりイメージ的なものでより正確には、「UCS と UTF」(貞廣さん)等を参照のこと)。一方、US-ASCII 文字はそのまま 1 文字のバイトコードのままで表現される。
その結果、UTF-8 の日本語文字は、論理的にバイトレベルで認識された場合には、3 文字のバイトコード(US-ASCII 文字)からなる文字列として認識される。すなわち、perl の関数 length
を使うと、日本語 1 文字が US-ASCII 文字 3 文字としてカウントされる。これを回避したい場合は、文字変数の UTF-8 フラグを有効にし(後述)、論理的にバイトレベルではなく、UTF-8 レベルで認識させるようにする必要がある。
ところで、length
のようなバイトレベルで論理的に認識されると不都合が生じるようなケースを除けば、たとえ UTF-8 の日本語文字列を扱うような場合であっても、特に UTF-8 フラグを有効にしなくとも事足りる。バイト文字列としてアウトプット(例えば print
)されたデータは、UTF-8 文字コードに対応したテキストエディタや HTML ブラウザ等で見れば、UTF-8 の日本語テキストとして表示される。元々バイトコード(US-ASCII 文字)の羅列として表現されているのが UTF-8 なわけだから、当然の話である。バイトコードの特定の箇所を、さらにバイトコード(US-ASCII 文字)の羅列によって符号化された日本語等の Unicode 文字であると認識して解釈するのは、テキストエディタ等の「表示段階」における話だからである。データ自体は、あくまでもバイトコード(US-ASCII 文字)の羅列なのだ。
では、なぜ、perl では UTF-8 を特別扱いして、UTF-8 フラグなんてものをプログラマに操作させようとするのか? これは先程述べたように、length
のような、バイトレベルと UTF-8 レベルで、解釈が 2 通りに分かれてしまうようなケースに関して、必要となってくる区別なのである。
そしてさらに、解釈が 2 通りに分かれていることから発生する問題があり、この問題に配慮することが、UTF-8 を扱おうとする Perl プログラマの主な仕事になるのではないかと思う。単に、変数に UTF-8 の日本語テキストを入力して、それをまた出力するような場合は、バイトコードのままでも、UTF-8 フラグを有効にしていても、気にしようが気にしまいがどちらでも問題は発生しない。問題は、「バイトコードとして認識されている文字列」と「UTF-8 として認識されている文字列」というそれぞれ別々の論理的なレベルで認識されているデータを結合するときに発生する。別々のやり方で処理されるべきものが、どちらか片方のやり方で認識されて取り扱われてしまうからである。──結果、文字化けを引き起こしてしまう。
以上のような問題を取り扱うに当たって、プログラマは、変数に UTF-8 フラグが立っているか、それとも立っていないかを意識して、そのフラグを適宜 On / Off する処理を行っていくことになる。UTF-8 フラグを On / Off する関数がそれぞれ、utf8::decode()
と utf8::encode()
である。
他に、utf8::upgrade()
と utf8::downgrade()
というものもあるが、僕の場合は、utf8::decode()
ぐらいしか使わない。utf8::encode()
を使う機会もほとんどない。
そもそも utf8::upgrade()
と utf8::downgrade()
に関しては、何のためにあるのかよく理解できていなかったのだが、どうやらこれらは、8bit で表現可能なバイトコードでありながら、7bit の US-ASCII ではない、ギリシャ文字やロシア文字や半角カタカナや特殊記号等の領域の文字に対して使うものらしい。つまり、00 ~ FF で表すことのできるバイトコードではある一方、US-ASCII(00 ~ 7F)の領域外(80 ~ FF)にある文字である。この領域は、ロケール(国)によって割り当てられている文字がてんでバラバラだったりする(すなわち背番号の登録が重複している)ややこしい領域で、US-ASCII の場合のように一意的に決まる領域ではないので、バイトレベルと UTF-8 レベルでマップされる背番号が異なってくる(Unicode 側では、背番号の重複は解消され、一意的なものとなっている。)。そこで、utf8::upgrade()
によって単に UTF-8 フラグを立てる以外に、背番号を UTF-8 のものに付け替える作業が必要となるわけである(この作業はロケールに左右されることになり、一対多の対応の関係にある変換となる)。utf8::downgrade()
は UTF-8 の背番号を持った、そのロケールにおけるバイトレベルでは 80 ~ FF の領域の文字として用いられる文字を、その 80 ~ FF の領域のバイトコードとして背番号を付け替えた上に UTF-8 フラグを外すという変換を行う(ロケールによって多対一の関係になる変換となる)。
つまり、日本語に関して言えば、Shift_JIS 等の 80 ~ FF の領域の半角カタカナを含んだ古いテキストデータを扱うような場合に、utf8::upgrade()
や utf8::downgrade()
のようなものを使う必要がでてくるかもしれないわけである。最初から、入出力するテキストデータを UTF-8 で作成していれば、気にする必要はない。UTF-8 として作成されたテキストデータであれば、半角カタカナのような文字は 80 ~ FF の領域のバイトコードとしてではなく、最初から Unicode のマップ上の UTF-8 の背番号でエンコードされているからである。
STDOUT から出力するような場合、UTF-8 フラグの立っていないバイトコードとして扱われる文字列は自動的に utf8::downgrade()
される。ところが、UTF-8 フラグを立てていないだけで、中身がバイトコード化できない(8bit のマップの背番号に割り付けできない)00 ~ FF よりも外の(wider)領域の文字を含むとき、フラグの状態と中身で矛盾した状態であることが発覚する。その場合、“Wide character in print”という警告を受けることになる。utf8::decode()
を施してちゃんと UTF-8 フラグを立てておけば、そのような矛盾は解消され、最初から UTF-8 文字列として扱って、自動的な utf8::downgrade()
を行おうとはしないので、警告されなくなる。
utf8::encode()
の方も、バイトレベルに処理レベルを戻す際に必要となるだけなので、バイトレベル→ UTF-8 レベルという方向性はあっても、UTF-8 レベル→バイトレベルという退化する方向性は普通は必要がないので、まず気にしなくていいのではないかと思う。
最後に、use utf8;
プラグマの使用は、ソースコード内に直接記述された UTF-8 の日本語文字が自動的に UTF-8 フラグが立った(utf8::decode()
された)状態で認識される効果があると考えればいい。外部から入力したデータの場合は、use utf8;
プラグマの有無に関わらず、utf8::decode()
でフラグを立てる必要があるので注意。また binmode
によって挙動を制御し、自動的に入出力の際に UTF-8 フラグを有効にする手も考えられる。
参考サイト:
Perl の Unicode support(貞廣さん)
perl5.8 の Unicode サポート(阿辺川さん)
UTF-8 と UTF16 の違いは?(@IT)
(last update: 2005-09-28)
この正誤表は <http://www.oreilly.co.jp/BOOK/errata/pperl.htm>(現在は消滅)から辿ることのできた「正誤表 4(1999-01-20)」までの修整に含まれていなかったものです。すでに絶版で、新しい第 3 版が出ている今となっては、出版元のオライリー・ジャパンおよび訳者の近藤さんによる正誤表のアップデートは期待できないと思いますので、ここに公開しておきたいと思います。
※「第 3 版」が出たと言っても、せっかく買って愛読してきた「改訂版」をまだ捨てるほどのことでもないという方は、他にもいらっしゃるのではないかと思います。
※畑的にはとりあえず Perl 6 の時代になってそれを反映したラクダ本が出る時がくるまでは「改訂版」をバイブルとして使い続けるつもりです。また、大改訂となることが予想される Perl 6 用のラクダ本は入手スピード重視で原書の方を買うことにするかもしれません。
原書(英語版)を持っているわけではないので、必ずしも誤植かどうかはわかりません。単なる僕の勘違いの場合もあるかも知れませんが、そのような場合や、他にも誤植にお気づきの場合は、ご一報くださると幸いです。
追記:ネットを検索したところ、PP2 Tips(NAKAMURA Hiroshi さん)というページを発見し、そこに多数の(オライリー・ジャパンの正誤表に)未掲載の情報が載っているのを見つけました。
【誤】 if (eof()) { print "=" x 30, "\n"; } 【正】 if (eof()) { print "=" x 30, "\n"; }
(2003-12-29)
-T
オプションを使う perl の汚染(taint)モードに関しては、ラクダ本に説明されている通りだが、本で読んだだけでは UNIX のセキュリティにそれほど詳しくない僕にはイマイチというか全くピンとこない。(汚染モードの件に限った話ではないが)実際に CGI プログラムを運用しながら -T
オプションを使ってみる中で本に書かれた説明の文意を理解していくしかないという面もあると思う。
どうやら、UNIX のセキュリティにおける要点は、パスワードの記述されたファイルを勝手に参照されたり書き換えられたりしないようにするという点にあるようである。そのパスワードファイルにアクセスするために、侵入者は、CGI のクエリ・コマンドなどに、細工を仕込むというやり方でアタックしようとするらしい。例えば、プログラムで用いる不特定のファイルのファイル名を CGI のクエリ・コマンドを通じて指定を受け取るような CGI プログラムの場合、そのファイル名に細工して、パスワードファイルにアクセスするような結果へと導こうとするのである。ちょっと考えると、ディレクトリが違うのだから、ファイル名でどうにかしようとしても無理なように思えるかもしれないが、シェルを通じてファイルにアクセスするような場合は、シェル特有のメタ文字解釈が間に生じるので、プログラマの意図しない動作をさせることが可能になり得るのである。シェルは、単純にファイル名を指定するだけではなくて、指定するファイル名を別のコマンド実行の結果として表現するような、コマンドの埋め込みのような真似が可能だからである。
そういったファイル操作の場合に、perl が直接ファイルにアクセスするのではなく、システムのシェルを経由してアクセスするような場合に、そのファイル指定に CGI のクエリ・コマンドで外部から受け取った、中身が不特定の文字列を使用するようなケースを検知し、ブロックするというのが汚染モードの機能のようだ。一方、perl プログラム内部で明確に記述されている特定の文字列によって指定されるファイルにアクセスするような場合であれば、問題がない。
通常、ファイルはパーミッション設定によって、パスワードファイルにアクセスするようなことはシステムレベルでブロックされる。つまり、特に perl 側において汚染モードでブロックする必要性はない。だが、たまに CGI の実行などでは、特殊な状況下ではオーナ権限で動作するような場面もあり得るので、パーミッション設定だけではブロックできない場合も有り得る。そこで、実ユーザ ID と実効ユーザ ID が異なって動作する場面において、perl は汚染モードで起動されるようになっているようである。一方、-T
オプションを指定した場合には、ユーザ ID の同異に関わらず、常に汚染モードで perl が起動される。
僕のように、自前でサーバを運営しているわけではなく、ただのレンタルサーバの間借り人のような場合であれば、システムのパスワードファイルのセキュリティの問題は、管理者側の関知するところであるから、直接は関係のない問題である。だが、そういう場合に、むしろ -T
オプションを指定した、常時汚染モードを CGI 運用で使う意義があるかもしれない。なぜなら、サーバのシステムのパスワード以外にも、CGI プログラム独自のパスワードを使ったセキュリティ機能を実装しているような場合がある。そのようなパスワードを記述したファイルに、予想外のやり方でアクセスされてしまわないように、-T
オプションによって常時汚染モード ON で運用できるプログラムコードを書くことによって、穴のない CGI 運用を実現することになるかもしれないからである。
「普通に rand()
を使うと、現在時刻の秒数によってパターン化された乱数が出てしまうので、srand()
を使って、乱数の初期値を変動させましょう」というような話を、まだ Web 上でも見かけますが、これは Perl 5.4 より前の古いバージョンにおける話です。Perl 5.4 以降では、必要ない上に、むしろ srand()
を下手に使って逆にパターン化された乱数になってしまう場合もあります。
Perl 5.8.7 のマニュアル情報によると、srand()
は引数なしのデフォルトで、srand( time ^ ($$ + ($$ << 15)) )
相当以上のことを行っているようです。Perl 5.4 以前の古い時代には引数なしのデフォルトの srand()
は srand(time)
だったので、それよりもランダムな種を自前で srand()
することによって与える必要があったわけです。しかし今や srand()
のデフォルトが十分ランダムなので、srand()
する必要はなく、rand()
だけ使った方がいいのです。自分で srand(time ^ $$)
したり srand( time ^ ($$ + ($$ << 15)) )
したりする必要はないということです。却って、初心者が複数回呼び出してはならない srand()
を知らずに複数回呼び出すことによって失敗する可能性の方が高いので、一般的には「srand()
は使うべきでない」と言っておくべきでしょう。
デフォルト以上の高度な乱数を得たい場合に限って、srand( time ^ $$ ^ unpack('%L*', `ps axww | gzip`) )
(これは UNIX システムでないと srand(time)
と同じ結果になり、デフォルトよりも悪い結果になる可能性もある)を使ったり、Math::TrulyRandom、Math::Random 等のモジュールを使う必要性が生じることになります。