このテキストは,私(岸 和孝)が著わし,社団法人日本印刷技術協会の定期セミナー「XML/SGMLデータ加工編」で使用しているものです。
このテキストは,あなたが個人で利用される場合に限り,ハードコピーを許可しますが,その他の目的での利用は禁止します。
お気付きの点がありましたら,ご一報ください。
Perlは,主にテキスト処理を行うプログラムです。しかしPerlは,通常のアプリケーションプログラムのように,対象となるファイルをメニューやダイアログで選べば,後は自動的に処理されるというものではありません。Perlは,「主にテキスト処理を行う『プログラム』を作るためのプログラム」です。一般に,プログラムを作る(組む)ことを「プログラミング」と言います。より専門的な分類では,Perlのプログラムを「スクリプト」,そのプログラミングを「スクリプティング」と言います。
スクリプティング(プログラミング)の習得は,決して楽なものではありません。仕事に役立つスクリプト(プログラム)が書けるようになるには不断の努力が必要です。しかし,一旦習得しさえすれば,仕事に大いに役立つはずです。スクリプティングは,コンピュータの持つパワーを最大限まで引き出します。自分の仕事に合ったアプリケーションプログラムが無いと嘆いていないで,そうしたスクリプトを自分で組んでみてください。
Perlスクリプトを動かすためには,WindowsではJPerl,MacOSではMacJPerlというPerlインタプリタ(通訳プログラム)をあらかじめインストールしておく必要があります。これらは,インターネットで無償で配布されています。日頃から技術情報や最新バージョンを入手するように努めてください。
Perlスクリプトは,特定の書き方(文法)で表現します。一つひとつのスクリプトはテキストファイルで表わします。スクリプトはテキストですので,任意のエディターで作ることができます。スクリプトにはスタイルは付けません。できあがったら,適当な意味のあるファイル名に拡張子“.PL”を付けて保存します。なお,MacJPerlには,スクリプトを扱うための専用のエディターが組み込まれていますので,それを利用してもいいでしょう。
Perlスクリプトは,「命令」の集まりで構成します。命令はコンピュータが行う動作を指示します。命令に制御が移ると,実行されます。制御は,スクリプトの先頭(上)から末尾(下)の方向へ移り,最後の命令まで進んで停止します。制御は,条件によって途中で分岐したり,ある命令の集まりを繰り返したりすることができます。
手始めに,“こんにちわ!”という文字列をディスプレイへ表示してみましょう。そのスクリプトは,次のように書きます[S111.PL]。
print 'こんにちわ!'; exit;
このスクリプトにおいて,print命令はデータの表示を指示し,exit命令はスクリプトの終了を指示します。それぞれの命令の末尾にはセミコロン“;”を付けます。一重引用符“'”で囲んだ範囲は「定数」です。「定数」は数値や文字列そのものです。この二つの命令は上から下へ向かって順々に実行されます。
Windowsでは,コマンドラインによって起動するか,バッチファイルへのドッラグ・アンド・ドロップによって起動します(その方法については後述します)。基本のコマンドラインは,“JPERL スクリプトファイル名”です。
MacOSでは,メニューによって起動するか,ドロップレットへのドッラグ・アンド・ドロップによって起動します(その方法については後述します)。
うまく動きましたか。もし書き方がおかしいと,Perlインタプリタはエラーメッセージを表示します。つまり,Perlインタプリタが理解できない命令を与えても実行できないからです。
次に,このスクリプトを“こんにちわ! ○△さん!”と表示するように書き換えてみましょう。“○△”は自由な名前にしてみてください。それは次のように書きます[S112.PL,S113.PL]。
print 'こんにちわ!'; print '○△さん'; exit;
print 'こんにちわ!○△さん!'; exit;
今度も,うまく動きましたか。次に,“こんにちわ!”の後に改行して,その次の行へ“○△さん!”を表示するようにします。改行動作は,文字列に行末符号“\n”を含めることによって指定します。この場合に,定数は二重引用符“"”で囲みます。それは次のように書きます[S114.PL]。
print "こんにちわ!\n○△さん!"; exit;
ところで,定数をなぜ二重引用符“"”で囲むのでしょうか。一重引用符“'”で囲むとどうなるのでしょうか。実際に次のように書いて動かしてみましょう[S115.PL]。
print 'こんにちわ!\n○△さん!'; exit;
こうすると,書いたとおりの文字列が表示され,行末符号になりません。つまり,一重引用符“'”で囲んだ定数では,文字列の内容そのものとなり,二重引用符“"”で囲んだ定数では,文字列の内容が一旦評価されます(これについては後述します)。
次の段階は,定数ではなく「変数」を扱います。Perlでは,数値や文字列の入れ物を「変数」と呼んでいます。変数は内容(または値)を入れる容器の名前である「変数名」で指示します。
先ず,キーボードから文字列を入力して,それを変数へ入れてみましょう。しかし,それだけでは変数に入った文字列を確認できませんので,それをディスプレイへ表示してみましょう。そのスクリプトは,次のように書きます[S121.PL]。print命令では変数名を指定します。いろいろな文字列を入力して表示内容を確認してください。キーボードからの入力の終了はEnterキー(Windowsの場合)またはReturnキー(MacOSの場合)です。
$text = <>; print $text; exit;
このスクリプトにおいて“$text”は変数名です。変数名は大文字・小文字が区別される英数字と“_”で構成します。名前は内容が連想できる分かりやすいものにしましょう。“<>”はキーボードから文字列を入力することを指示し,“=”は右辺から左辺への「代入」を指示します。つまり,“$text = <>”という命令は,キーボードから文字列を1行分入力して,それを変数$textへ入れる意味になります。この二つの命令は上から下へ向かって順々に実行されますが,キーボードからの入力が終了しないと,次のprint命令へは進みません。
次に,2個の文字列を入力し,それらを連結して表示してみましょう。それは次のように書きます[S122.PL]。変数$word1と変数$word2に順々に文字列を入力します。次に“$word1.$word2”という「式」で文字列の連結を指示します。“.”が連結の「演算子」です。また,“<>”は入力の演算子です。こうして連結した文字列を変数$textへ代入します。
$word1 = <>; $word2 = <>; $text = $word1.$word2; print $text; exit;
これでうまく動くか確かめてみましょう。たぶん連結したはずの二つの文字列は改行で二つに分かれてしまうでしょう。その原因は,キーボードの入力を終了させたEnterキーまたはReturnキーに対応する行末符号が変数へ入力されてしまうからです。例えば,“こんにちわ!”を入力すると変数$word1に“こんにちわ!\n”が代入され,“○△さん!”を入力すると変数$word2に“○△さん!\n”が代入されます。したがって,連結した結果は“こんにちわ!\n○△さん!\n”ということになります。
前述の問題を避けるために,行末符号を取り除くchop命令があります。次のように書いて動かしてみましょう[S123.PL]。
$word1 = <>; chop $word1; $word2 = <>; chop $word2; $text = $word1.$word2; print $text; exit;
行末符号“\n”はOS固有の表わし方になります。
JPerl 復帰符号・改行符号の組(CR+LF) MacJPerl 1個の復帰符号(CR)
chop命令は行末符号を一つの文字として削除します。したがって,スクリプトのOS間の互換性を維持するために,chop命令を必ず使うようにしましょう。
文字列の連結は別の形でも指示できます。それは次のように書きます[S124.PL]。二重引用符“"”で囲んだ文字列の中に変数名があると,それは単なる文字列そのものではなく,変数名の箇所がその内容(値)に置き換えられます。これを「変数展開」と言います。
$word1 = <>; chop $word1; $word2 = <>; chop $word2; $text = "$word1$word2\n"; print $text; exit;
実際のテキスト処理では,入力したデータの内容によって処理の仕方を変えたい場合があります。そうした処理の流れの変更を指示する形を「分岐制御」と呼んでいます。
次のスクリプト[S131.PL]は,最初にキーボードから適当な文字列を入力します。それが“おはよう”であれば“おはようございます”と表示し,そうでなければ“こんにちわ”と表示を変えます。いろいろな文字列を入力して確認してください。
$word = <>;
chop $word;
if ( $word eq 'おはよう' ) {
print "おはようございます\n";
}
else {
print "こんにちわ\n";
}
exit;
このスクリプトにおいて“if ( E ) { B1 } else { B2 }”が分岐制御です。B1,B2の部分を「ブロック」と言い,分岐した際に実行させる命令の並びから構成します。E部分を「分岐条件」と言います。“$word eq 'おはよう'”を分岐判定式と言い,変数$wordが“おはよう”と等しいかを調べます(“eq”は文字列比較の演算子)。その条件が成立すれば,if(もしEならば)のブロックB1(“おはようございます”と表示する)を実行します。成立しなければ,else(そうでなければ)のブロックB2(“こんにちわ”と表示する)を実行します。
C1 eq C2 文字列C1と文字列C2が等しいか C1 ne C2 文字列C1と文字列C2が等しくないか C1 gt C2 文字列C1が文字列C2より大きいか C1 ge C2 文字列C1が文字列C2より大きいか等しいか C1 lt C2 文字列C1が文字列C2より小さいか C1 le C2 文字列C1が文字列C2より小さいか等しいか
前述のスクリプトでさらに分岐する条件を増やしてみましょう。次のスクリプト[S132.PL]において,if(もしEならば)で分岐条件が成立しなければ,elsif(そうでなくEならば)の分岐条件が調べられます。その条件が成立すれば,elsifのブロック(“こんにちわ”と表示する)を実行します。成立しなければ,else(そうでなければ)のブロック(“どうも”と表示する)を実行します。この“elsif ( E ) { B }”は幾つでも書けますので,いわゆる「場合分け」の表現ができます。次のスクリプトをさらに書き換えてみましょう。
$word = <>;
chop $word;
if ( $word eq 'おはよう' ) {
print "おはようございます\n";
}
elsif ( $word eq 'こんにちわ' ) {
print "こんにちわ\n";
}
else {
print "どうも\n";
}
exit;
実際のテキスト処理では,入力したデータの内容によって処理の仕方を変えたい場合に,「ある変数の内容が文字列そのものに等しいか」ではなく,「ある変数の内容が文字列のパターンに等しいか」を調べる必要がでてきます。例えば,あるデータが日付の形をしているかどうかを調べる場合に,考えうる日付をすべて書き並べて調べることは事実上不可能です。Perlでは,データの形(パターン)を「正規表現」として表わすことができ,それを条件の判定式とすることができます。
次のスクリプト[S141.PL]は,最初にキーボードから適当な文字列を入力します。それが日付の形(パターン)であれば“これは日付です”と表示し,そうでなければ“これは不明です”と表示します。このif分岐制御の分岐判定式“$text =~m!\d\d\d\d年\d\d月\d\d日!”は,変数$textが正規表現“\d\d\d\d年\d\d月\d\d日”に一致するかを調べます(“=~m”は照合の演算子)。“\d”は1個の数字に一致します。“!”は正規表現を区切る文字です(他の本では“/”がよく使われていますが,ここでは“!”を使います)。したがって,“1999年12月31日”や“2000年01月01日”のようなデータはパターンに一致します。
$text = <>;
chop $text;
if ( $text =~m!\d\d\d\d年\d\d月\d\d日! ) { print "これは日付です\n"; }
else { print "これは不明です\n"; }
exit;
前述のスクリプト[S141.PL]は“2000年1月1日”のようなデータには一致しません。そこで,さらに詳しく正規表現を与えるようにしましょう。次のスクリプト[S142.PL]の正規表現“\d\d\d\d年\d?\d月\d?\d日”の“?”は直前の“\d”が省略可能であることを示します。したがって,“2000年1月1日”のようなデータにも一致します。なお,正規表現では“2000年13月32日”のような存在しない日付を調べることはできません。これはif分岐制御を使って調べることになります。
$text = <>;
chop $text;
if ( $text =~m!\d\d\d\d年\d?\d月\d?\d日! ) { print "これは日付です\n"; }
else { print "これは不明です\n"; }
exit;
次のスクリプト[S143.PL]は,入力した日付“yyyy年mm月dd日”を“yyyy/mm/dd”の形式へ変えて表示します。正規表現をかっこ“(”と“)”で囲むと,それらに一致した内容が変数$1,$2,$3に順々に代入されます。これを「グループ」と言います。グループで取り出した内容(値)が代入される変数を「特殊変数」と言います。
$text = <>;
chop $text;
if ( $text =~m!(\d\d\d\d)年(\d?\d)月(\d?\d)日! ) {
print "$1/$2/$3\n";
}
else {
print "これは不明です\n";
}
exit;
次のスクリプト[S144.PL]は,SGML文書インスタンス(SGMLタグが付いたデータ)の中からP要素(P開始タグとP終了タグに囲まれたデータ)を検出します。正規表現の“.”は1個の文字(改行符号以外)に対応し,“+”は直前の“.”が1個以上繰り返すことを示します。したがって,“直前のデータ<P>これはサンプルです。</P>直後のデータ”を入力すれば“これはサンプルです。”の部分だけがグループとして取り出されます。
$text = <>;
chop $text;
if ( $text =~m!<P>(.+)</P>! ) { print "$1\n"; }
else { print "条件に該当したタグはありません\n"; }
exit;
前述のスクリプト[S144.PL]には問題があります。例えば,“直前のデータ<P>これは1行目です。</P><P>これは2行目です。</P>直後のデータ”を与えてみてください。期待と違って“これは1行目です。</P><P>これは2行目です。”が取り出されてしまいます。つまり,“.+”が期待した部分に対応しないからです。こうした場合は,次のスクリプト[S145.PL]のように書きます。この正規表現は“これは1行目です。”に対応します。“.*”は文字が0個以上繰り返すことを示し,それに“?”を付けることで,最小の照合を行うことを指示します。
$text = <>;
chop $text;
if ( $text =~m!<P>(.*?)</P>! ) { print "$1\n"; }
else { print "条件に該当しません\n"; }
exit;
正規表現の対応関係を知るために,次のスクリプト[S146.PL]について考えてみましょう。正規表現の“^”は行の先頭に対応し,“$”は行の末尾に対応します。“直前のデータ<P>これは1行目です。</P><P>これは2行目です。</P>直後のデータ”を与えてみてください。次のように表示されるでしょう。このことを前提にして,グループによる要素の取り出しを行うようにしてください。
1:直前のデータ 2:<P>これは1行目です。</P> 3:<P>これは2行目です。</P>直後のデータ
$text = <>;
chop $text;
if ( $text =~m!^(.*?)(<P>.*?</P>)(.*?)$! ) {
print "1:$1\n";
print "2:$2\n";
print "3:$3\n";
}
else {
print "条件に該当しません\n";
}
exit;
E 正規表現の要素 (E) Eをグループとして検出する E? 省略可能なEに対応する E1|E2 E1又はE2に対応する E* 0個以上繰り返すEに対応する E+ 1個以上繰り返すEに対応する E{m} m個繰り返すEに対応する E{m,n} m個以上n個以内繰り返すEに対応する . 1個の文字(改行符号以外)に対応する \d 1個の数字に対応する \w 1個の英数字に対応する \s 1個の空白,タブ,改行,復帰のいずれかに対応する \t 1個のタブ符号に対応する \n 1個の改行符号に対応する \\ 1個の文字“\”に対応する \xh1h2 16進数字列h1h2で表わした文字に対応する [C1C2…] C1,C2,…のいずれか一つに対応する ^ 行の先頭に対応する $ 行の末尾に対応する
ここまでのスクリプトは,1回実行すると停止してしまいましたが,実際のテキスト処理では,幾つものデータに対して,ある処理を繰り返し実行することが要求されます。それで本当の生産性が上がるわけです。そうした繰り返しを指示する形を「反復制御」と言います。
次のスクリプト[S151.PL]は,最初にキーボードから適当な数値を入力します。次に,1から順々に数えてその数値まで,その数値分の“*”からなる行の表示を繰り返します。
$max = <>;
chop $max;
for ( $i = 1; $i <= $max; $i++ ) {
$bar = '*' x $i;
print "$bar\n";
}
exit;
このスクリプトにおいて“for ( E ) { B }”が反復制御です。B部分を「ブロック」と言い,反復して実行させる命令の並びから構成します。E部分を「反復条件」と言い,3個の式から構成します。“$i = 1”を「初期化式」,“$i <= $max”を「脱出判定式」,“$i++”を「増分式」と言います。
この動きは,次のようになりま。反復の最初に変数$iに1を代入します。次に変数$iが変数$maxより小さいか等しいかを調べます(“<=”は数値比較の演算子)。その条件が成立すれば,ブロックBを1回実行します。次に変数$iに1を加えます(“++”は1を加える演算子)。そして再び,変数$iと変数$maxを比較します。その条件が成立すれば同じことを行い,成立しなければ,この反復を終わります。つまり,繰り返しを止めて脱出するわけです。
ブロックBでは,変数$bar に文字“*”を変数$i回繰り返し生成した文字列を代入し(“x”は文字列の演算子),それを表示します。
このスクリプトで変数$maxに数値ではない文字列を入力したらどうなるでしょうか。たぶんPerlは何もしないでしょう。Perlでは数値と文字列の区別はスクリプトの文脈から自動的に行われます。例えば,加減乗除算の演算子や数値比較の演算子を使った文脈での変数の内容は数値とみなされます。
V = E 変数Vへ式Eの値を代入する V ++ 変数Vへ1を加え代入する V -- 変数Vから1を減じ代入する V += E 変数Vへ式Eの値を加え代入する V -= E 変数Vから式Eの値を減じ代入する V *= E 変数Vに式Eの値を乗じ代入する V /= E 変数Vを式Eの値で除し代入する V .= E 変数Vへ文字列Eを連結し代入する
N1 + N2 数値N1と数値N2を加えた和 N1 ++ 数値N1に1を加えた和 N1 - N2 数値N1から数値N2を減じた差 N1 -- 数値N1から1を減じた差 N1 * N2 数値N1と数値N2を乗じた積 N1 ** N2 数値N1を数値N2でべき乗した積 N1 / N2 数値N1を数値N2で除した商 N1 % N2 数値N1を数値N2で除した剰余
N1 == N2 数値N1と数値N2が等しいか N1 != N2 数値N1と数値N2が等しくないか N1 > N2 数値N1が数値N2より大きいか N1 >= N2 数値N1が数値N2より大きいか等しいか N1 < N2 数値N1が数値N2より小さいか N1 <= N2 数値N1が数値N2より小さいか等しいか
C1 . C2 文字列C1と文字列C2を連結する C x N 文字列CをN回繰り返し生成する
次のスクリプト[S152.PL]は,前述のfor反復制御と同じ機能を持つ別の反復制御です。このスクリプトにおいて“while ( E ) { B }”が反復制御です。B部分が「ブロック」で,E部分が「反復条件」です。while反復制御では脱出判定式“$i <= $max”だけが指定できます。したがって,初期化式“$i = 1”と増分式“$i++”は命令として指定しています。for反復制御とwhile反復制御における初期化,脱出判定,増分の実行順序を比べてみてください。
$max = <>;
chop $max;
$i = 1;
while ( $i <= $max ) {
print "$i\n";
$i++;
}
exit;
スクリプトの要素は,1個以上の空白で区切ります。分岐制御や反復制御では,開始と終了の括弧を上下方向で合わせ,その中を字下げします。
例えば,
while ( $i <= $max ) {
print "$i\n";
$i++;
}
または,
while ( $i <= $max )
{
print "$i\n";
$i++;
}
は,
while ( $i <= $max ) { print "$i\n"; $i++; }
のように書いても構いませんが,ブロックが長くなると見にくいでしょう。
実際のテキスト処理は,キーボードからの入力ではなく,専らファイルからの入力・出力となるでしょう。Perlの処理対象となるファイルは,原則的にテキストデータが行単位で順次に並んだテキストファイルです。ファイルからデータを入力する際は,開ける(open),読み込む(<>),閉じる(close)の三つの操作で行います。ファイルへデータを出力する際は,開ける(open),書き出す(print),閉じる(close)の三つの操作で行います。
▼図1.6 ファイルの入力
次のスクリプト[S161.PL]は,while反復制御でデータを繰り返し,ファイルの終了(EOF:End Of File)まで読み込みます。while反復の脱出判定式“$text = <IN>”はデータが1行分読み込めた場合に成立し,EOFを検出した時点に成立しません。このスクリプトは,最初にキーボードから適当なファイル名を入力します。次に,open命令でファイルを開けます。“<”は入力することを,“IN”はファイルを指すファイルハンドラーを指定します。そして,ファイルから順々にデータを読み込み,EOFを検出するまでデータの表示を繰り返します。“<IN>”はファイルハンドラーINが指すファイルからの入力を指定します。最後に,close命令でファイルを閉じます。
$file = <>;
chop $file;
open IN, "<$file";
while ( $text = <IN> ) {
chop $text;
print "$text\n";
}
close IN;
exit;
前述のスクリプト[S161.PL]で,ファイル名の指定を間違えて,存在しないファイルを指定するとどうなるでしょうか。たぶんPerlは何もしないでしょう。そうした操作ミスへの対処するために,次のように書いたほうがいいでしょう[S162.PL]。つまり,open命令の実行が失敗した場合に“ファイル`$file'が開けません”というメッセージを表示をして停止させます。“unless ( E ) { B }”は,分岐条件が成立しなければ(開けないならば)ブロックBを実行します。
$file = <>;
chop $file;
unless ( open IN, "<$file" ) { print "ファイル`$file'が開けません"; exit; }
while ( $text = <IN> ) {
chop $text;
print "$text\n";
}
close IN;
exit;
Perlでは,ファイルへのパスはOS固有の表わし方になります。
JPerl ドライブ名:ディレクトリ名\ディレクトリ名\…\ファイル名 MacJPerl ボリューム名:フォルダー名:フォルダー名:…:ファイル名
次のスクリプト[S163.PL]は,指定したファイルの内容を検索し,該当行を表示します。キーボードから入力ファイル名と検索の正規表現を入力する直前に,それぞれ「何」をキー入力するかを示すメッセージを表示します。これを「プロンプト(入力督促)」と言います。また,検索がすべて終了したことを最後にメッセージで表示します。こうした表示は,自分で使う場合も便利ですし,何よりも他人にスクリプトを利用してもらう際の配慮です。if分岐制御の分岐判定式“$text =~m!$exp!o”は,変数$textが変数$expの内容である正規表現に一致するかを調べます(“o”は変数展開を指示するオプション)。
print "入力ファイル:"; $infile = <>; chop $infile;
unless ( open IN, "<$infile")
{ print "ファイル`$infile'が開けません"; exit; }
print "検索の正規表現:"; $exp = <>; chop $exp;
$lno = 0;
while ( $text = <IN> ) {
chop $text;
$lno++;
if ( $text =~m!$exp!o ) { print "$lno行目:$text\n"; }
}
close IN;
print "処理が終了しました。\n";
exit;
次のスクリプト[S164.PL]は,ファイルの単純なコピーを行います。このスクリプトは,最初にキーボードから入力ファイル名と出力ファイル名を入力します。次に,open命令でファイルをそれぞれ開けます。入力側が“<”と“IN”で,出力側が“>”と“OUT”で対照的に指定します。print命令でファイルハンドラーOUTを指定することによってファイルへの書き出しとなります(ディスプレイへの表示はされません)。このスクリプトの注釈“# ここで$textに対して何かの処理を行う。”で示されたことに注目してください。このスクリプトは,大半のテキスト処理の骨組みになるでしょう。
print "入力ファイル:"; $infile = <>; chop $infile;
unless ( open IN, "<$infile" )
{ print "ファイル`$infile'が開けません"; exit; }
print "出力ファイル:"; $outfile = <>; chop $outfile;
if ( $infile eq $outfile )
{ print "同じファイル`$infile'は指定できません"; exit; }
unless ( open OUT, ">$outfile" )
{ print "ファイル`$outfile'が開けません"; exit; }
while ( $text = <IN> ) {
chop $text;
# ここで変数$textに対して何らかの処理を行う。
print OUT "$text\n";
}
close IN;
close OUT;
print "処理が終了しました。\n";
exit;
今まで見てきたPerlの変数は,1個の数値や文字列の内容(または値)を入れる容器でした。これを「スカラー変数」と言います。変数名の接頭辞の“$”はScalarの“S”に由来します。次に学ぶのは,より便利なデータの容器となる「アレイ変数」と「連想アレイ変数」です。
アレイ変数は,幾つかのスカラー変数をアレイ(配列)要素として一括して入れる容器です。これは本棚のような形を想像するといいでしょう。個々の本(要素)は順序番号(0,1,2,3,…)順に並んでいます。アレイ変数は“@list”のように変数名の接頭辞が“@”です。これはArrayの“A”に由来します。ここでは,アレイは「リスト」と同じ意味で使います。個々の要素は順序番号を「添え字」“[0],[1],[2],[3],…”で表わし,“$list[$i]”のように指定します。
▼図1.7 スカラー変数とアレイ変数
連想アレイ変数は,アレイ変数と同様に,幾つかのスカラー変数をアレイ(配列)要素として一括して入れる容器です。連想アレイ変数は“%table”のように変数名の接頭辞が“%”です。アレイ変数と違って,個々の本(要素)は“我が輩は猫である,田園の憂鬱,ファウスト,…”のような本の名前(キー)で指します。その「添え字」は“{'我が輩は猫である'},{'田園の憂鬱'},{'ファウスト'},…”で表わし,“$table{$k}”のように指定します。
次のスクリプト[S171.PL]は,指定したディレクトリからそれに含まれているファイル名の並びを取り出し表示します。最初にキーボードから適当なディレクトリを入力します。次に,opendir命令でディレクトリを開けます。“DIR”はディレクトリのためのハンドラーです。そして,readdir命令でディレクトリの内容(つまり,ファイル名や下位のディレクトリ名の並び)を一括して読み込み,アレイ変数@filesへ代入します。その後にclosedir命令でディレクトリを閉じます。次に,アレイ変数@filesを構成する個々の要素(スカラー変数)をforeach反復制御で扱います。“foreach S ( A ) { B }”は,アレイ変数Aから順序番号0の要素の内容(値)を取り出し,スカラー変数Sへ代入し,ブロックBを実行します。そして再び,順序番号1の要素の内容を取り出し,スカラー変数Sへ代入し,ブロックBを実行します。この反復はアレイ変数Aからすべての要素を取り出し終わった時に終わります。ファイル名の並びだけを取り出すために,ディレクトリ名,ファイル名以外の要素を取り除きます。例えば,Winowsの場合には“.”や“..”を除きます。こうした分岐条件は“$f eq '.' || $f eq '..'”と表わせます。ディレクトリ名の検出は,ファイルテストによって行います。next命令は途中で分岐した際に反復制御へ直ちに戻ることを指示します。
print "ディレクトリ:"; $dir = <>; chop $dir;
opendir DIR, $dir;
@files = readdir DIR;
closedir DIR;
foreach $f ( @files ) {
# ディレクトリ名,ファイル名以外の要素の検出
if ( $f eq '.' || $f eq '..' ) { next; } # Winowsの場合
# if ( $f eq "Icon\n" ) { next; } # MacOSの場合
# ディレクトリ名の検出
if ( -d "$dir\\$f" ) { next; } # Winowsの場合
# if ( -d "$dir:$f" ) { next; } # MacOSの場合
print "$f\n";
}
exit;
Perlでは,ディレクトリ(フォルダー)の内容はOS固有の表わし方になります。
JPerl ディレクトリ名,ファイル名以外に“.”や“..”を含む MacJPerl ディレクトリ名,ファイル名以外に“Icon\n”を含む
-e F パスFのファイルは存在しているか -d F パスFはディレクトリか -T F パスFのファイルはテキストか -B F パスFのファイルはバイナリーか
E1 && E2 条件E1かつ条件E2であるか(両方の条件が成立しているか) E1 || E2 条件E1または条件E2か(どちらかの条件が成立しているか) ! E 条件Eでないか(その条件が成立していないか)
次のスクリプト[S172.PL]は,前述のスクリプト[S171.PL]のforeach反復制御“foreach S ( A ) { B }”をfor反復制御“for ( E ) { B }”で書き直したものです。アレイ変数@filesを構成する個々の要素(スカラー変数)を“$files[$i]”という添え字を付けた形で参照します。順序番号に対する初期化式が“$i = 0”,脱出判定式が“$i <= $#files”,増分式が“$i++”になります。ここで,スカラー変数$#filesはアレイ変数@filesの要素数から1を減じた数(つまり,末尾の順序番号)を表わしています。
print "ディレクトリ:"; $dir = <>; chop $dir;
opendir DIR, $dir;
@files = readdir DIR;
closedir DIR;
for ( $i = 0; $i <= $#files; $i++ ) {
# ディレクトリ名,ファイル名以外の要素の検出
if ( $files[$i] eq '.' || $files[$i] eq '..' ) { next; }# Winowsの場合
# if ( $files[$i] eq "Icon\n" ) { next; } # MacOSの場合
# ディレクトリ名の検出
if ( -d "$dir\\$files[$i]" ) { next; } # Winowsの場合
# if ( -d "$dir:$files[$i]" ) { next; } # MacOSの場合
print "$files[$i]\n";
}
exit;
次のスクリプト[S173.PL]は,前述のスクリプト[S172.PL]のforeach反復制御“foreach S ( A ) { B }”を別の書き方“foreach S ( N1 .. N2 ) { B }”で書き直したものです。添え字にするスカラー変数Sに順序番号N1からN2までを順次に増分して代入し,ブロックBを実行します。“foreach S ( A ) { B }”がすべての要素を取り出すのに対して,“foreach S ( N1 .. N2 ) { B }”は指定した範囲の要素だけを取り出すことができます。
print "ディレクトリ:"; $dir = <>; chop $dir;
opendir DIR, $dir;
@files = readdir DIR;
closedir DIR;
foreach $i ( 0 .. $#files ) {
# ディレクトリ名,ファイル名以外の要素の検出
if ( $files[$i] eq '.' || $files[$i] eq '..' ) { next; }# Winowsの場合
# if ( $files[$i] eq "Icon\n" ) { next; } # MacOSの場合
# ディレクトリ名の検出
if ( -d "$dir\\$files[$i]" ) { next; } # Winowsの場合
# if ( -d "$dir:$files[$i]" ) { next; } # MacOSの場合
print "$files[$i]\n";
}
exit;
次のスクリプト[S174.PL]は,Excelなどの作表ソフトから出力されるタブ区切りされたデータを整形して表示します。先ず,タブ区切りされた項目はsplit命令によって分解し,その結果をアレイ変数@atextへ代入します。split命令の“/\t/”は分解する時の区切り文字としてタブ符号“\t”を指定します。次に,タブの位置を合わせて表示するために,printf命令で変数$itemをフォーマット“%-20s”(文字列,左揃え,20字幅の意味)によって書式付けます。このprintf命令は,C言語の標準ライブラリにあるものと同じ機能です。
print "入力ファイル:"; $infile = <>; chop $infile;
unless ( open IN, "<$infile" )
{ print "ファイル`$infile'が開けません"; exit; }
while ( $text = <IN> ) {
chop $text;
@atext = split( /\t/, $text );
foreach $item ( @atext ) { printf "%-20s", $item; }
printf "\n";
}
close IN;
print "処理が終了しました。\n";
exit;
▼図1.7.4 split命令とjoin命令
フォーマットは,対応する変数に対して“%mx”の形で指定します。mは表示の幅を指示し,正数の場合は右揃え,負数の場合は左揃えになります。xは次のように変数の表示形式を指示します。詳細は,C言語の標準ライブラリを参照してください。
c 文字 d 10進数 x 16進数 s 文字列
次のスクリプト[S175.PL]は,ファイルの単純なコピーを行うスクリプト[S164.PL]を基にして,テキストデータに含まれているかもしれないSGMLデータ実体の変換をします。例えば,“<”という文字はSGML文書インスタンスでは通常タグの開き文字に相当しますので,文字としては代替表現であるデータ実体参照“<”へ変換する必要があります。
このスクリプトにおいて,最初に,連想アレイ変数%refにSGMLデータ実体参照となる特定の文字の組を設定します。“'<' => '<'”は,キーが“<”で,それに対応する要素の内容(値)が“<”であることを表わします。同様に,他の代替表現すべき文字についても設定します。変数$textに対する処理では,先ず,split命令によって1文字単位の要素からなるアレイ変数に分解します。split命令の区切り文字は1文字単位なので“//”という指定になります。それらの文字を一つずつ取り出し,変換すべき文字かどうかを調べます。そして,文字列連結“.=”によってスカラー変数$textを再び組み立てます。
%ref = (
'<' => '<',
'>' => '>',
'&' => '&'
);
print "入力ファイル:"; $infile = <>; chop $infile;
unless ( open IN, "<$infile" )
{ print "ファイル`$infile'が開けません"; exit; }
print "出力ファイル:"; $outfile = <>; chop $outfile;
unless ( open OUT, ">$outfile" )
{ print "ファイル`$outfile'が開けません"; exit; }
while ( $text = <IN> ) {
chop $text;
# 変数$textに対する処理
@atext = split( //, $text );
$text = '';
foreach $c ( @atext ) {
if ( $ref{$c} ) { $text .= $ref{$c}; } else { $text .= $c; }
}
print OUT "$text\n";
}
close IN;
close OUT;
print "処理が終了しました。\n";
exit;
テキスト処理に関する幾つものスクリプトを書いていると,必ず決まりきったスクリプトの部分があることに気付きます。スクリプティングの生産性を上げるために,そうした汎用性がある部分を応用の主たるスクリプト(メインスクリプト)から独立させたものが「サブルーチンスクリプト」です。
サブルーチンスクリプトSUBRは,“&SUBR( E1, E2, … )”の形で指示します。ここで,E1,E2,…を「引き数」と言います。引き数とは,メインスクリプト側からサブルーチンスクリプトへ引き渡す変数のことです。引き数は,特殊変数@_を経由してサブルーチンスクリプトで受け取られます。その引き渡し方法には,変数の内容(値)を渡す「値渡し」と変数の記憶場所を渡す「参照渡し」があります。値渡しの引き数は“$E”や“@E”の形で指定し,参照渡しの引き数は“*E”の形(これを「型グロブ」と言います)で指定します。
▼図1.8 サブルーチンへの引き数の値渡し
次のスクリプト[S181.PL]は,キーボードから入力した適当なテキストのバイト数と字数を計算します。バイト数は,Perlの組み込み関数であるlength()で得られますが,字数を得る関数は組み込まれていませんので,これをサブルーチンスクリプトklengthとします。変数$textを値渡しします。サブルーチンスクリプトklengthは受け取った引き数から計算した字数をreturn命令によってメインスクリプトへ返します。local()はその変数名がサブルーチンスクリプト側だけの名前であることを指定し,メインスクリプトの変数名と重複しないようにしています。このことによって変数名の衝突から生じる混乱を避けることができます。さらに他人にサブルーチンスクリプトを利用してもらう際の配慮です。
# メインスクリプト
print "テキスト:"; $text = <>; chop $text;
$leng = length( $text );
print "バイト数:$leng\n";
$kleng = &klength( $text );
print "字数:$kleng\n";
exit;
# サブルーチンスクリプト
sub klength {
local( $data ) = $_[0];
local( @kdata );
@kdata = split( //, $data );
return $#kdata + 1;
}
次のスクリプト[S182.PL]は,キーボードから入力した適当なテキストのコンマで3桁ごとに位取りします。これは値渡しでもできますが,ここでは参照渡しのサブルーチンスクリプトで表現します。変数$numは“*num”で参照渡しされ,“*n”で受け取られます。サブルーチンスクリプトcommasでの変数$nは,メインスクリプトでの変数$numと同じ記憶場所を指すことになります。“$n =~s!(.*\d)(\d\d\d)!$1,$2!”は,パターン照合を使った文字列の置換で(“=~s”は置換の演算子),正規表現“(.*\d)(\d\d\d)”に一致した時に,その一致した範囲の文字列を“$1,$2”で置き換えます。従って,変数$1が第1のグループで取り出した文字列に置き換わり,次に“,”が置かれ,そして変数$2が第2のグループで取り出した文字列に置き換わります。こうして3桁ごとにコンマが挿入されるわけです。
# メインスクリプト
print "数値:"; $num = <>; chop $num;
&commas( *num );
print "$num\n";
exit;
# サブルーチンスクリプト
sub commas {
local( *n ) = @_;
while ( $n =~s!(.*\d)(\d\d\d)!$1,$2! ) { ; }
}
次のスクリプト[S183M.PL,S183S.PL]は,今日の日付を取り出します。サブルーチンスクリプトdateは,Perlの組み込み関数であるlocaltime(time)で得られる時刻を見やすい形へ変換します。sprintf命令は,printf命令と同様のフォーマット指定によって文字列へ整形します。その結果をreturn命令によってメインスクリプトへ返します。このサブルーチンスクリプトはメインスクリプトS183M.PLとは異なるファイルS183S.PLとして存在しています。メインスクリプトはそれが一つのスクリプトになるようにPerlインタプリタにrequire命令によって指示します。ファイルS183S.PLの最後の行はEOFを表わす“1;”とし,Perlインタプリタにその旨を通知します。こうした仕組みによってサブルーチンスクリプトの共用化が容易に図れるようになっています。
# メインスクリプト require "S183S.PL"; $yymmdd = &date(); print "$yymmdd\n"; exit;
# サブルーチンスクリプト
sub date {
local( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time);
$year += 1900;
$mon++;
$atime = sprintf "%02d年%02d月%02d日 %02d時%02d分%02d秒"
, $year, $mon, $mday, $hour, $min, $sec;
return $atime;
}
1;
大半のテキスト処理では,処理対象のファイルを指定することが多く,それを効率的に進める必要があります。そうしたファイル名をキーボードから入力するのは,なるべく避けたいものです。ここでは,Perlスクリプトの実行を効率化する方法について説明します。
WindowsやMacOSでは,アプリケーションプログラムはそのアイコンに対してドラッグ・アンド・ドロップされたファイルを処理することが行われています。Perlスクリプトでも同様の操作を実現できます。
Windowsでは,ドラッグ・アンド・ドロップされたファイルのパスをコマンドラインの引き数へ変換するバッチファイル[S21.BAT]を用意します。MacOSでは,PerlスクリプトをPerlドロップレットの形式で保存します。いずれも,そのアイコンに対してドラッグ・アンド・ドロップすることによってPerlスクリプトを起動できるようになります。次のスクリプト[S21.PL]において,ドラッグ・アンド・ドロップした(幾つかの)ファイルは,アレイ変数@ARGVにパスの形で引き渡され,そのファイルの処理を行うサブルーチンスクリプトprocへ引き渡されます。
▼図2.1 ドラッグ・アンド・ドロップによる引き数の渡し
REM D:\JPERL5\BIN JPERLのバイナリが置かれたディレクトリ REM C:\SCRIPT S21.PLのスクリプトが置かれたディレクトリ REM このコマンドラインのパラメターは多くても5個くらい D:\JPERL5\BIN\JPERL C:\SCRIPT\S21.PL %1 %2 %3
# メインスクリプト
$deli = "\\"; # Windowsの場合
#$deli = ':'; # MacOSの場合
if ( $#ARGV >= 0 ) {
foreach $df ( @ARGV ) {
if ( -d $df ) {
opendir DIR, $df;
@files = readdir DIR;
closedir DIR;
foreach $f ( @files ) {
if ( $f eq '.' || $f eq '..' ) { next; } # Windowsの場合
# if ( $f eq "Icon\n" ) { next; } # MacOSの場合
if ( -d $f ) { next; }
&proc( "$df$deli$f" ); # ディレクトリがドラッグ・アンド・ドロップされた場合
}
}
else {
&proc( $df ); # ファイルがドラッグ・アンド・ドロップされた場合
}
}
}
else {
# 引き数がない時の処理
}
print "処理が終了しました。\n";
exit;
# サブルーチンスクリプト
sub proc {
local( $file ) = $_[0];
# $fileに対する処理
}
大半のテキスト処理では,出力ファイル名は,入力ファイル名と関連付けされます。例えば,入力ファイル“SAMPLE.TXT”にタグ付けした結果が出力ファイル“SAMPLE.SGM”になるように,拡張子を変更します。次のスクリプト[S22.PL]のサブルーチンスクリプトpathは,引き数のパスからディレクトリ,ファイル名,拡張子を別々に取り出し,得られたファイル名に新たな拡張子を付けて出力ファイル名に組み立てます。
# メインスクリプト
print "入力ファイル:"; $infile = <>; chop $infile;
unless ( open IN, "<$infile")
{ print "ファイル`$infile'が開けません"; exit; }
($dir, $fname, $ext) = &path($infile);
$outfile = "$dir\\$fname.SGM"; # Windowsの場合
#$outfile = "$dir:$fname.SGM"; # MacOSの場合
print "出力ファイル:$outfile\n";
unless ( open OUT, ">$outfile" )
{ print "ファイル`$outfile'が開けません"; exit; }
while ( $text = <IN> ) {
chop $text;
# ここで変数$textに対して何らかの処理を行う。
print OUT "$text\n";
}
close IN;
close OUT;
print "処理が終了しました。\n";
exit;
# サブルーチンスクリプト
sub path {
local( $name ) = $_[0];
# パスの区切り文字の正規表現
local( $deli ) = "\\\\"; # Windowsの場合
# local( $deli ) = ':'; # MacOSの場合
if ( $name =~m!^(.*)$deli(.*?)\.(.*?)$! ) { return ($1, $2, $3); }
elsif ( $name =~m!^(.*?)\.(.*?)$! ) { return ('', $1, $2); }
elsif ( $name =~m!^(.*)$deli(.*?)$! ) { return ($1, $2, ''); }
else { return ('', $name, ''); }
}
テキスト処理で作業用ファイルを使う必要が生じた場合に,固定的な作業用ファイルを設定するよりも,一時的に生成し,使用後は消去するほうがいいでしょう。次のスクリプト[S23.PL]のサブルーチンスクリプトtempfileは,カレントディレクトリ内に他のファイルと決して衝突しない作業用ファイル名を作り出します。その作業用ファイルを開いたり閉じたりするのはメインスクリプト側で行います。最後に作業用ファイルを消去するには,unlink命令によって指示します。この命令は強力で,どんなファイルでも(システムファイルですら)無条件に消去してしまいますので,慎重に使用してください。
# メインスクリプト
$workfile = &tempfile();
open WORK, ">$workfile";
# 何らかの処理を行う
close WORK;
unlink $workfile;
exit;
# サブルーチンスクリプト
sub tempfile {
local( $temp );
while (1) {
$temp = 'WORK'.int( rand( 100 ) ).'.DAT';
unless ( -e $temp ) { last; }
}
return $temp;
}
テキスト処理では,多くのテキストファイルを扱いますので,それらをディレクトリ単位で管理する必要が生じます。次のスクリプト[S24.PL]のサブルーチンスクリプトallfilesは,指定したディレクトリ内の(最下位のディレクトリまで調べて)すべてのファイル名を取り出します。これを利用したファイル管理ユーティリティが考えられるでしょう。
# メインスクリプト
print "ディレクトリ:"; $d = <>; chop $d;
@all = ( );
&allfiles( *all, $d, "\\" ); # Windowsの場合
&allfiles( *all, $d, ':' ); # MacOSの場合
foreach $f ( @all ) { print "$f\n"; }
exit;
# サブルーチンスクリプト
sub allfiles {
local( *fnames, $cdir, $deli ) = @_ ;
local( @dnames, $dir, @files, $df ) ;
@dnames = ( ) ;
push( @dnames, $cdir) ;
while ( 1 ) {
$dir = pop( @dnames );
if ( $dir eq '' ) { last ; }
unless (opendir(DIR, $dir))
{ print " ディレクトリ`$dir'が開きません。\n" ; last ; }
@files = readdir DIR;
closedir DIR;
foreach $df ( @files ) {
if ( $df eq '.' || $df eq '..' ) { next; } # Windowsの場合
# if ( $df eq "Icon\n" ) { next; } # MacOSの場合
if ( -d "$dir$deli$df" ) { push( @dnames, "$dir$deli$df" ); }
else { push( @fnames, "$dir$deli$df" ); }
}
}
}
▼図2.4 push/pop命令とunshift/shift命令
次の命令は,変数$textに含まれているかもしれないすべての“、”を“,”へ,すべての“.”を“。”へ置換します。
$text =~tr!、.!,。!;
次の命令は,変数$textに含まれているかもしれないすべての半角英大文字を小文字へ変換します。
$text =~tr!A-Z!a-z!;
次の命令は,変数$textに含まれているかもしれないすべての全角英数字を半角文字(ASCII)へ変換します。
$text =~tr!0-9A-Za-z!0-9A-Za-z!;
次の命令は,変数$textに含まれているかもしれないすべての“JAGAT”を“(社)日本印刷技術協会”へ置換します。
$text =~s!JAGAT!(社)日本印刷技術協会!g;
次のサブルーチンスクリプト[S35S.PL]は,引き数のデータに含まれているかもしれないすべての半角仮名文字を全角へ変換し,その結果を返します。
sub hkconv {
local( $t ) = $_[0];
local( $k0 ) = '。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン″°';
local( @k2 ) = (
'カ″', 'キ″', 'ク″', 'ケ″', 'コ″',
'サ″', 'シ″', 'ス″', 'セ″', 'ソ″',
'タ″', 'チ″', 'ツ″', 'テ″', 'ト″',
'ハ″', 'ヒ″', 'フ″', 'ヘ″', 'ホ″',
'ハ°', 'ヒ°', 'フ°', 'ヘ°', 'ホ°',
'ウ″' );
local( $k1 ) = 'ガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポヴ';
local( @k, $nt, $c );
@k = split( //, $t );
$nt = '';
foreach $c ( @k ) {
if ( $c ge ' && $c le ' ) { $c = substr( $k0, (ord($c)-161)*2, 2 ); }
$nt .= $c;
}
foreach $i ( 0 .. $#k2 ) { $nt =~s!$k2[$i]!substr($k1, $i*2, 2)!eg; }
return $nt;
}
次のサブルーチンスクリプト[S36S.PL]は,引き数のデータの文体パターンを調べてHTMLタグを付け,その結果を返します。ここで用意されている文体の例は,次のようなものです(□は全角の間隔文字を表わしています)。
1□Perlの基本 1.2□変数への入力・変数からの出力 1.8.2□コンマで位取り 次のメインスクリプトは,……
sub htag {
local( $t ) = $_[0];
if ($t =~m!^(\d+) (.+)$!) { $nt = "<H1>第$1章 $2</H1>"; }
elsif ($t =~m!^\d+\.\d+ (.+)$!) { $nt = "<H2>$1</H2>"; }
elsif ($t =~m!^\d+\.\d+\.\d+ (.+)$!) { $nt = "<H3>$1</H3>"; }
else { $nt = "<P>$t</P>"; }
return $nt;
}
次のサブルーチンスクリプト[S37S.PL]は,引き数のファイルを入力し,その文体パターンを調べてHTMLタグを付け,その結果をHTMLファイルとして出力します。これはこのテキスト(PerlText.TXT)をHTML化するのに用いたスクリプトです。文体パターンの検査に不十分な個所はありますが,その他のテキスト文書のHTML化にも応用できるでしょう。いろいろ工夫してみてください。
sub proc {
local( $file ) = $_[0];
( $dir, $fname, $ext ) = &path( $file );
$outfile = "$dir\\$fname.HTM"; # Windowsの場合
# $outfile = "$dir:$fname.HTM"; # MacOSの場合
# 目次の生成
open IN, "<$file";
@toc = ();
$glo = 0;
$pre = 0;
$tab = 0;
$title = '題名未設定';
push( @toc, "<DL>\n" );
while ( $text = <IN> ) {
chop $text;
if ( $text eq '' ) { next; }
if ( $text =~m!^ +(図.+) (.+)$! ) { next; }
if ( $text =~m!^ +(.+)$! ) { $title = $1; next; }
if ( $text =~m!^\t(.+)\t(.+)$! ) { unless ( $tab ) { $tab = 1; } next; }
if ( $tab ) { $tab = 0; }
if ( $text =~m!^▲$! ) { $pre = 0; next; }
if ( $pre ) { next; }
if ($text =~m!^(\d+) (.+)$!) { push( @toc, "<DT><A HREF=\"#$text\">第$1章 $2</A>\n" ); }
elsif ($text =~m!^(\d+\.\d+) (.+)$!) { push( @toc, "<DD><A HREF=\"#$text\">$1 $2</A>\n" ); }
elsif ($text =~m!^\d+\.\d+\.(\d+) (.+)$!) { push( @toc, "<DD><A HREF=\"#$text\"> ($1) $2</A>\n" ); }
elsif ($text =~m!^( .+)$!) { ; }
elsif ($text =~m!^▽(.+)$!) { $glo = 1; }
elsif ($text =~m!^△$!) { $glo = 0; }
elsif ($text =~m!^▼(.+)$!) { $pre = 1; }
elsif ($text =~m!^▼$!) { $pre = 1; }
else {
if ( $glo ) { ; }
else { push( @toc, "<DT><A HREF=\"#$text\">$text</A>\n" ); }
}
}
push( @toc, "</DL>\n" );
close IN;
# プロローグの生成,スタイルの指定,目次の出力
open OUT, ">$outfile";
print OUT <<PROLOG;
<HTML>
<HEAD>
<STYLE TYPE="text/css">
<!--
BODY {
background-color:white;
}
H1 {
font-size:20pt;
text-align:center;
color:gold;
}
H2 {
font-size:18pt;
color:blue;
}
H3 {
font-size:16pt;
color:teal;
}
H4 {
font-size:14pt;
color:green;
}
H5 {
font-size:12pt;
color:limegreen;
}
P {
font-size:12pt;
line-height:18pt;
color:black;
}
PRE {
font-size:12pt;
line-height:14pt;
white-space:pre;
margin-left:14pt;
border:thin solid blue;
background-color:blue;
color:white;
}
-->
</STYLE>
<TITLE>$title</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<CENTER>
<H1>$title</H1>
<HR>
目 次
</CENTER>
PROLOG
foreach ( @toc ) { print OUT $_; }
# 本文のタグ付け
open IN, "<$file";
$glo = 0; # 要約のスイッチ
$pre = 0; # プリフォーマットのスイッチ
$tab = 0; # テーブルのスイッチ
while ( $text = <IN> ) {
chop $text;
if ( $text eq '' ) { next; }
#
$text =~s!&!&!g;
$text =~s!<!<!g;
$text =~s!>!>!g;
#
$anchor = $text;
#
unless ( $pre ) {
$text =~s!(Windows)!<FONT COLOR="teal">$1</FONT>!g;
$text =~s!(MacOS)!<FONT COLOR="teal">$1</FONT>!g;
$text =~s!(MacJPerl)!<FONT COLOR="teal">\x1e</FONT>!g;
$text =~s!(JPerl)!<FONT COLOR="teal">\x1f</FONT>!g;
$text =~s!(Perl)!<FONT COLOR="teal">$1</FONT>!g;
$text =~s!\x1e!MacJPerl!g;
$text =~s!\x1f!JPerl!g;
$text =~s!(\w+命令)!<B>$1</B>!g;
$text =~s!(\w+分岐制御)!<B>$1</B>!g;
$text =~s!(\w+反復制御)!<B>$1</B>!g;
$text =~s!(正規表現)!<B>$1</B>!g;
$text =~s!(\$\w+)!<B>$1</B>!g;
$text =~s!(\$\#\w+)!<B>$1</B>!g;
$text =~s!(\@\w+)!<B>$1</B>!g;
$text =~s!(\%\w+)!<B>$1</B>!g;
$text =~s!(\*\w+)!<B>$1</B>!g;
$text =~s!“([^”]+)”!“<FONT COLOR="blue">$1</FONT>”!g;
$text =~s![([^]]+)]![<FONT COLOR="green">$1</FONT>]!g;
$text =~s!「([^」]+)」!「<FONT COLOR="teal">$1</FONT>」!g;
}
#
if ( $text =~m!^ +図(.+) (.+)$! ) { print OUT "<CENTER>\n";
print OUT "<P>▼図$1 $2</P>\n";
$fig = $1; $fig =~tr!\.!_!;
print OUT "<IMG SRC=\"Fig$fig.GIF\">\n";
print OUT "</CENTER>\n";
next;
}
if ( $text =~m!^ +(.+)$! ) { next; }
if ( $text =~m!^\t(.+)\t(.+)$! ) {
unless ( $tab ) { print OUT "<BLOCKQUOTE>\n<TABLE BORDER=1>\n"; $tab = 1; }
print OUT "<TR><TD BGCOLOR=\"yellow\" NOWRAP>$1<TD>$2\n";
next;
}
if ( $tab ) { print OUT "</TABLE>\n</BLOCKQUOTE>\n"; $tab = 0; }
if ( $text =~m!^▲$! ) { print OUT "</PRE>\n"; $pre = 0;
next;
}
if ( $pre ) { print OUT "$text\n"; next; }
if ($text =~m!^(\d+) (.+)$!) { print OUT "<HR>\n<A NAME=\"$anchor\"></A>\n<H2>第$1章 $2</H2>\n"; }
elsif ($text =~m!^(\d+\.\d+) (.+)$!) { print OUT "<A NAME=\"$anchor\"></A>\n<H3>$1 $2</H3>\n"; }
elsif ($text =~m!^\d+\.\d+\.(\d+) (.+)$!) { print OUT "<A NAME=\"$anchor\"></A>\n<H4>($1) $2</H4>\n"; }
elsif ($text =~m!^( .+)$!) { print OUT "<P>$1</P>\n"; }
elsif ($text =~m!^▽(.+)$!) { print OUT "<H5>●$1</H5>\n"; $glo = 1; }
elsif ($text =~m!^△$!) { $glo = 0; }
elsif ($text =~m!^▼(.+)$!) { print OUT "<H5>■$1</H5>\n<PRE>\n"; $pre = 1; }
elsif ($text =~m!^▼$!) { print OUT "<PRE>\n"; $pre = 1; }
else {
if ( $glo ) { print OUT "<P>$text</P>\n"; }
else { print OUT "<HR>\n<A NAME=\"$anchor\"></A>\n<H2>$text</H2>\n"; }
}
}
# エピローグの生成
print OUT <<EPILOG;
<HR>
<CENTER>
<P>(C)2000,KISI ( kisi\@nifty.com )</P>
</CENTER>
</BODY>
</HTML>
EPILOG
close IN;
close OUT ;
}
変数 文字列や数値を入れる容器の(記憶場所に付けた)名前 定数 特定の文字列や数値そのもの 正規表現 文字列のパターン(かたち)を表現したもの 演算子 特定の演算を指示するもの 式 変数,定数,演算子を組み合わせて結果を求めるようにしたもの 命令 スクリプトの実行単位,式にセミコロンを続けたもの ブロック 命令の並びを“{”と“}”でくくったもの 分岐制御 場合分けとなるスクリプトの部分を表わすもの 反復制御 繰り返しとなるスクリプトの部分を表わすもの サブルーチン 補助的なスクリプトの部分
$i スカラー変数 @list アレイ変数,個々の要素は順序番号で並び,$list[$i]で参照 %table 連想アレイ変数,個々の要素はキーと結び付き,$table{$k}で参照 *var 型グロブ,参照渡し $1 特殊変数,正規表現に一致した第1グループの値,スカラー変数 @_ サブルーチンへ引き渡される引き数の並び,アレイ変数 @ARGV 起動時にスクリプトへ引き渡される引き数の並び,アレイ変数
-123 10進数,符号は“+”又は“-” 0xff 16進数 '見出し\n' 一重引用符でくくった文字列,文字列そのもの,この値は“見出し”と“\n” "$heading\n" 二重引用符でくくった文字列,文字列は変数展開される,この値は変数$headingが“見出し”ならば“見出し”と行末符号 "\n" 行末符号(JPerlではCR+LF,MacJPerlではCR) "\t" タブ符号
V = E 変数Vへ式Eの値を代入する V ++ 変数Vへ1を加え代入する V -- 変数Vから1を減じ代入する V += E 変数Vへ式Eの値を加え代入する V -= E 変数Vから式Eの値を減じ代入する V *= E 変数Vに式Eの値を乗じ代入する V /= E 変数Vを式Eの値で除し代入する V .= E 変数Vへ文字列Eを連結し代入する
N1 + N2 数値N1と数値N2を加えた和 N1 ++ 数値N1に1を加えた和 N1 - N2 数値N1から数値N2を減じた差 N1 -- 数値N1から1を減じた差 N1 * N2 数値N1と数値N2を乗じた積 N1 ** N2 数値N1を数値N2でべき乗した積 N1 / N2 数値N1を数値N2で除した商 N1 % N2 数値N1を数値N2で除した剰余
N1 == N2 数値N1と数値N2が等しいか N1 != N2 数値N1と数値N2が等しくないか N1 > N2 数値N1が数値N2より大きいか N1 >= N2 数値N1が数値N2より大きいか等しいか N1 < N2 数値N1が数値N2より小さいか N1 <= N2 数値N1が数値N2より小さいか等しいか
-e F パスFのファイルは存在しているか -d F パスFはディレクトリか -T F パスFのファイルはテキストか -B F パスFのファイルはバイナリーか
E1 && E2 条件E1かつ条件E2であるか(両方の条件が成立しているか) E1 || E2 条件E1または条件E2か(どちらかの条件が成立しているか) ! E 条件Eでないか(その条件が成立していないか)
C1 . C2 文字列C1と文字列C2を連結する C x N 文字列CをN回繰り返し生成する
S =~m/P/ スカラー変数Sの文字列を正規表現Pによって照合する
E 正規表現の要素 (E) Eをグループとして検出する E? 省略可能なEに対応する E1|E2 E1又はE2に対応する E* 0個以上繰り返すEに対応する E+ 1個以上繰り返すEに対応する E{m} m個繰り返すEに対応する E{m,n} m個以上n個以内繰り返すEに対応する . 1個の文字(改行符号以外)に対応する \d 1個の数字に対応する \w 1個の英数字に対応する \s 1個の空白,タブ,改行,復帰のいずれかに対応する \t 1個のタブ符号に対応する \n 1個の改行符号に対応する \\ 1個の文字“\”に対応する \xh1h2 16進数字列h1h2で表わした文字に対応する [C1C2…] C1,C2,…のいずれか一つに対応する ^ 行の先頭に対応する $ 行の末尾に対応する
S =~tr/C1/C2/ スカラー変数Sの文字列に含まれているすべての文字C1を文字C2へ置き換える S =~s/P/C/ スカラー変数Sの文字列を正規表現Pによって照合し,一致した箇所を文字列Cへ置き換える S =~s/P/C/g 同,一致した箇所をすべて置き換える S =~s/P/C/e 同,文字列Cを式として評価して後に置き換える A =(V1,V2,…) アレイ変数Aへ値の並びV1,V2,…を代入する
chop(S) スカラー変数Sの末尾の一文字を削除する length(S) スカラー変数Sのバイト長を返却する substr(S,P,L) スカラー変数Sの文字位置Pから長さLだけの文字列を返却する。代入式の左辺ではその文字列の位置を指示する split(/S/,C) スカラー変数Sを文字Cで分割し,アレイにして返却する join(C,A) アレイ変数Aの要素を文字Cで接続し,スカラーにする push(A,S) アレイ変数Aの末尾にスカラー変数Sを追加する pop(A) アレイ変数Aの末尾要素を削除して返却する unshift(A,S) アレイ変数Aの先頭にスカラー変数Sを追加する shift(A) アレイ変数Aの先頭要素を削除して返却する each(A) 連想アレイ変数Aのキーと対応する値のすべての対を返却する keys(A) 連想アレイ変数Aのすべてのキーを返却する values(A) 連想アレイ変数Aのすべての値を返却する
print D データDを表示する print H D ファイルHへデータDを書き出す printf F,D 書式Fに合わせてデータDを表示する printf H F,D 書式Fに合わせてファイルHへデータDを書き出す open H,N ファイルNを開けてハンドラーHで指す close H ファイルHを閉じる opendir H,N ディレクトリNを開けてハンドラーHで指す readdir H ディレクトリHから内容を読み込む closedir H ディレクトリHを閉じる <H> ファイルHからデータを読み込み返却する
next 反復制御の先頭へ移る last 反復制御から脱出する exit スクリプトを停止する while (E) B 条件式Eが真である間,ブロックBを繰り返し行う foreach S (A) B アレイ変数Aの要素の一つをスカラー変数Sに順次取り出し,その都度ブロックBを行う if (E) B1 else B2 もし条件式Eが真であればブロックB1を行い,そうでなければブロックB2を行う unless (E) B もし条件式Eが真でなければブロックBを行う for (E1;E2;E3) B 初期化式E1,増分式E3として,条件式E2が真である間,ブロックBを繰り返し行う
sub SUBR B ブロックBを行うサブルーチンをSUBRとして定義する &SUBR(E1,E2,..) サブルーチンSUBRを引き数E1,E2,..で呼び出す return E 関数として呼び出した側へEの値を返却する
(2000年7月記)