Perlとは?


目次

はじめに ▼
Perlの概要 ▼
行末符号の変換 ▼
正規表現によるパターン照合 ▼
メールの見出し抽出 ▼
フォルダー内をすべて検索 ▼
日本語データの処理 ▼
バイナリデータの処理 ▼
プリプレスへの応用 ▼
Perlの習得 ▼
さいごに ▼

はじめに

 私(岸 和孝)にとってPerlは手持ちの数ある応用ソフトの中で最強のツールです。仕事で扱っている様々なテキストデータはすべてPerlで編集加工しています。また,私の研究テーマであるSGMLに関連する各種のユーティリティをPerlで試作しています。

 私がPerlと出会ったのは,JAGATが開催するSGML関連セミナーを担当する時でしたから,もう2年ほど前になります。JAGATの研究調査部の方から「Perlがこれからのプリプレスで強力なツールになるだろう」という示唆があり,実際に使ってみて,その便利さに驚き,その後は仕事に欠かせないツールとなりました。そうしたことから,もっと多くの方々にPerlを知ってもらいたいと思います。


Perlの概要

 Perlは,テキストデータを編集加工するのに優れたソフトウェアです。他のソフトウェアでも同じことができるかもしれませんが,Perlの方が簡単に表現でき,しかも便利で強力な機能を持っているので,大半のアプリケーションを実現できるでしょう。「Perlで処理できないのは画像だけ」と言っても過言ではありません。

 Perlは,米国のLarry Wall氏によって1989年に開発され,その後も機能強化が続けられているフリーウェア(使用料が不要のソフトウェア)で,多くのボランティアによって各種のプラットフォーム(UNIX,MS-Windows,MacOS)に移植され広く使われています。

 MacPerlは,Matthias Neeracher氏,Tim Endres氏によって移植され,さらに田中良知氏,清水周一氏によって日本語化されました。

 Perlは,機種間の互換性をかなり高く保っており,環境をあまり選びません。現時点におけるPerlの最新バージョンは5です。

 Perlは,C言語によく似た書き方をするプログラム用言語の一種ですが,MacOSのAppleScriptと同じスクリプト用言語に分類されます。つまり,Perlは,翻訳即実行(Compile and Go)方式で動きます。なお,スクリプトはプログラムは同じ意味です。

 現在,Perlが注目されている理由の一つは,今までのテキスト処理のツール,例えば,Grep,Awk,Tclに比べると,かなり大きなアプリケーション(私の経験では2500行余のスクリプト)が表現でき,しかも実用的な速度で処理できることです。もう一つの理由は,インターネットのウェブサーバーにおいてユーザーのリクエストを処理するCGI(Common Gateway Interfaceの略)と呼ばれるプログラムを書くのに向いていることです。


行末符号の変換

 前書きはこれくらいにして,簡単なPerlスクリプトについて説明しましょう。次のスクリプトは,テキストファイルのレコードの行末符号をMac形式(CR)からDOS形式(CR LF)への変換を行います。

行末符号をMac形式からDOS形式への変換するスクリプト

 [1]  $infile = <> ;
 [2]  $outfile = $infile.'.WORK' ;
 [3]  open(IN, "<$infile") ;
 [4]  open(OUT, ">$outfile") ;
 [5]  while ($text = <IN>) {
 [6]    chop($text) ;
 [7]    print(OUT "$text\x0d\x0a") ;
 [8]  }
 [9]  print(OUT "\x1a") ;
[10]  close(IN) ;
[11]  close(OUT) ;
[12]  unlink($infile) ;
[13]  rename($outfile, $infile) ;
[14]  exit ;

注)スクリプトの左端の番号は説明のためのものです。

 このスクリプトは,次のような意味です。

 処理対象となるファイルの名前をキーボードから受け取り,変数$infileに入れます[1]。

 一時的な出力先のファイル名を作り,変数$outfileに入れます[2]。

 入力ファイルと出力ファイルをそれぞれ開きます[3-4]。

 ファイルに含まれるレコードについて一つずつ処理します[5-8]。

 先ず,レコードを変数$textへ読み込み,ファイルの終端であれば繰り返しを終わらせます[5]。

 次に,変数$textの内容の行末符号(CR)を除去し[6],代わりに変数$textの内容に行末符号(CR LF)を付けてファイルへ出力します[7]。

 すべてのレコードについて変換が終わったら,ファイルの終端符号(SUB)をファイルへ出力します[9]。

 ファイルをそれぞれ閉じます[10-11]。

 入力ファイルを消去して[12],出力ファイルの名前を入力ファイルの名前に置き換えます[13]。

 最後に,スクリプトを終了します[14]。


正規表現によるパターン照合

 Perlにおける特徴の一つとして,パターン照合の機能が挙げられます。そのパターンとは,特定の一つの文字列だけでなく何通りかの文字列を意味します。例えば,“96/06/13”や“97/12/31”のような日付を表わす文字列のパターンは“\d\d/\d\d/\d\d”と表わします。ここで“\d”は一つの数字を,“/”は一つのスラッシュ記号を意味します。こうした表現を「正規表現式」と呼んでいます。

 正規表現式では文字の種類,パターンの繰り返しや選択などが表現できます。例えば,“Perlは.+です”は,“Perlは”と“です”の間に任意の文字(ピリオド記号で表わす)が一つ以上ある(プラス記号で表わす)ことを意味します。従って,このパターンは“Perlはスクリプト用言語です”や“Perlは便利です”のような文字列と一致します。

 このパターン照合は極めて高速ですので,大量の文書における文章の書き換えや特徴的な文体(特徴のある書き方の文章やタグ付けされた文章)の検出などに利用できます。


メールの見出し抽出

 パターン照合を使ったPerlスクリプトを説明します。次のスクリプトは,パソコン通信のニフティの通信ログデータを解析してメールを受信した部分を検出し,見出し情報だけを抽出し表示します。

 通信ログデータとしては,次のような文字列を想定しています。

     ∫
5  INET GATE  INT00103  97/05/30 17:37
題名:Test
Date: Fri, 30 May 1997 17:48:54 +0900
From: Kazutaka Kisi <kisi@mb.infoweb.or.jp>
To: NAG00251@niftyserve.or.jp
     ∫
 (中略)
     ∫
action:DELete
     ∫

メールの見出しを抽出するスクリプト

 [1]  $logfile = <> ;
 [2]  open(IN, "<$logfile") ;
 [3]  while ($log = <IN>) {
 [4]    if ($log =~m!\s+(\w\w\w\d\d\d\d\d)\s+(\d\d/\d\d/\d\d)\s+(\d\d\:\d\d)$!) {
 [5]      $from1 = $1 ; $date = $2.' '.$3 ;
 [6]      next ;
 [7]    }
 [8]    if ($log =~m!題名:(.*)$!) { $sub = $1 ; next ; }
 [9]    if ($log =~m!From:(.*)$!)  { $from2 = $1 ; next ; }
[10]    if ($log =~m!^action:DELete$!) {
[11]      if ($from2 eq '') { $from = $from1 ; }
[12]      else { if ($from2 =~m!<(.+@.+)>!) { $from = $1 ; } }
[13]      print("$date $from $sub\n") ;
[14]      next ;
[15]    }
[16]  }
[17]  close(IN) ;
[18]  exit ;

注)スクリプトの左端の番号は説明のためのものです。

 このスクリプトは,次のような意味です。なお,このスクリプトでは丸括弧で囲んだ正規表現式がいくつも使われていますが,それは,そのパターンの箇所に一致した文字列を組み込み変数の$1や$2で取り出すことを意味します。

 処理対象となるファイルの名前をキーボードから受け取り,変数$logfileに入れます[1]。

 入力ファイルを開きます[2]。

 ファイルに含まれるレコードについて一つずつ処理します[3-16]。

 先ず,レコードを変数$logへ読み込み,ファイルの終端であれば繰り返しを終わらせます[3]。

 次に,変数$logの内容を調べて四通りの場合分けをします[4-15]。 メールの最初のレコードの場合,発信人と日付を変数の$from1と$dateへそれぞれ取り出します[4-7]。

 題名を表わすレコードの場合,題名を変数$subへ取り出します[8]。  インターネット経由であることを表わすレコードの場合,発信人を変数$from2へ取り出します[9]。

 メールの最後のレコードの場合[10],ニフティ内のメール[11]か,インターネット経由のメール[12]かで発信人の抽出方法を分けます。そして,確定したメールの見出し情報を表示します[13]。

 すべてのレコードについて解析が終わったら,ファイルを閉じます[17]。

 最後に,スクリプトを終了します[18]。


フォルダー内をすべて検索

 次に,パターン照合を使った少し複雑なPerlスクリプトについて説明します。次のスクリプトは,テキストデータに含まれる指定した文字列の検索を行いますが,テキストエディターにあるような簡単な検索ではありません。文字列だけでなくパターンでも検索できます。また,一つのファイルについてだけでなくフォルダー内に存在するすべてのテキストファイルについて検索できます。

フォルダー内をすべて検索するスクリプト

 [1]  print(" パターン > ") ;   $exp = <> ; chop($exp) ;
 [2]  print(" フォルダー > ") ; $dir = <> ; chop($dir) ;
 [3]  opendir(DIR, "$dir") ;
 [4]  @files = readdir(DIR) ;
 [5]  closedir(DIR) ;
 [6]  foreach $file (@files) {
 [7]    $infile = $dir.':'.$file ;
 [8]    if (-d $infile || -B $infile) { next ; }
 [9]    unless (-T $infile) { next ; }
[10]    open(IN, "<$infile") ;
[11]    print(" 検索 $infile ...") ;
[12]    $find = 0 ;
[13]    while ($text = <IN>) {
[14]      if ($text =~m!$exp!) { $find ++ ; }
[15]    }
[16]    close(IN) ;
[17]    if ($find > 0) { print(" $find 件あり\n") ; } 
[18]              else { print(" 該当なし\n") ; }
[19]  }
[20]  exit ;

注)スクリプトの左端の番号は説明のためのものです。

 このスクリプトは,次のような意味です。

 最初に,パターンとフォルダー名(パス名)をキーボードから受け取り,変数の$expと$dirにそれぞれ入れます[1-2]。

 次に,そのフォルダーに含まれるファイル名の並びをアレイ(配列)として変数@filesへ読み込みます。[3-5]。

 そして,フォルダーに含まれるファイルについて一つずつ処理します[6-19]。

 最初に,ファイルのパス名を求め[7],それがテキストファイルかどうか調べます[8-9]。

 テキストファイルであれば,そのファイルを開きます[10]。

 検索するファイルの名前を表示します[11]。

 パターン照合の件数を数える変数$findをゼロにしておきます[12]。

 ファイルに含まれるレコードについて一つずつ処理します[13-15]。

 先ず,レコードを変数$textへ読み込み,ファイルの終端であれば繰り返しを終わらせます[13]。

 変数$textにパターン$expが含まれているか調べ,含まれていれば変数$findに1を加えます[14]。

 すべてのレコードについての走査が終わったら,ファイルを閉じ[16],パターン照合の結果(件数)を表示します[17-18]。

 すべてのファイルについての走査が終わったら,スクリプトを終了します[20]。


日本語データの処理

 Perlは英語のデータしか扱えませんが,多くのボランティアによって日本語処理対応版に翻案され,JPerlと名前を変えて流通しています。JPerlでは日本語データ(いわゆる全角の漢字と半角の英数字などの混在した文字列)について,英語のデータと同じように,変数と定数で扱え,入出力,パターン照合,文字列の大小比較,文字列の結合や分離などの処理ができるようになっています。

 MS-Windows版のJPerlとMacOS版のMacPerlは,シフトJIS符号系で表わされた日本語データが扱えるようになっています。従ってJIS符号系のテキストデータを扱う場合は,シフトJIS符号系へ変換するスクリプト(サブルーチン)が必要になります。


バイナリデータの処理

 Perlは,テキストデータだけでなく,画像データのようなバイナリデータも扱えるように,いくつかの組み込み関数を用意しています。私は,PICT形式,GIF形式,TIFF形式,JPEG形式といった画像データや,EPWING形式のデータを解析するスクリプトを書いたことがあります。バイナリデータも仕様が明らかであれば,その処理はそれほど難しいことではありません。


プリプレスへの応用

 私は,SGMLに関連する各種のユーティリティをPerlで試作し,プリプレスの仕事で使っています。現在,SGML検証解析系の出力データを組版出力系へ変換するフォーマッターSgmlFormを“http://hyperarchive.lcs.mit.edu/HyperArchive/Archive/text/sgml-tool.hqx”で公開しています。このフォーマッターは,表組み,画像データの取得,エスケープ文字の置換,PageMakerやpTeXなどのタグ付きテキストの出力などの機能を備えています。ぜひご利用下さい。

 また,プレーンテキストの書式を解析して自動的にタグ付けしたり,外字をデータ実体参照へ変換したりできるコンバーターTidyTextなども試作しており,近々公開する予定です。


Perlの習得

 前述のいくつかの例で見たとおり,Perlの書き方はC言語によく似ていますので,C言語を知っている方にとっては極めて習得しやすい言語だと思います。もちろんC言語を知らない方にとっても習得しやすい特徴を備えています。

 第一の特徴は,スクリプトの誤り除去(デバッグ)が簡単であることです。未完成の(言い換えれば,少々いい加減な)手続きは,C言語では致命的な結果(暴走によるフリーズ)しか生じませんが,Perlではそれなりの結果を生じ,めったに暴走することはありません。つまりPerlでは,初心者でも試行錯誤しながらスクリプトを作っていくことができます。

 第二の特徴は,テキスト処理が簡単に表現できることです。前述したパターンを照合したりする処理は,C言語ではかなり複雑な手続きになりますが,Perlでは簡単です。言い換えれば,Perlではそうした複雑さに由来するような誤り(バグ)が生じにくいと言えます。

 第三の特徴は,より複雑なスクリプトで用いられるサブルーチンや関数が簡単に表現できることです。サブルーチンの組み立ては,C言語ではペダンチック(形式が厳密)な作業になりますが,Perlではきわめて現実的な(言い換えれば,初心者でも何とかできる)緩い表現になっています。

 Perlのスクリプティング(プログラミング)の習得に当たっては,他の技術の習得と同じで「習うより慣れろ」です。小さなスクリプトをいくつも書いたり,他の人の書いたスクリプトを理解したり書き換えたりしているうちに,技術は次第に上達していくでしょう。

 Perlについてさらに勉強する場合には,いくつかの参考書が出版されていますので,皆さんの能力やアプリケーションに合ったものを選ぶといいでしょう。その中でもLarry Wall氏が著した“Programming Perl”(邦訳「Perlプログラミング」,近藤嘉雪訳,ソフトバンク,1993)は,Perlの正確な文法(バージョン4)や豊富なヒントを収録したものでユーザー必携の書と言えます。


さいごに

 Perlのスクリプトは,テキストファイル形式ですから,その大きさは通常のアプリケーションプログラムに比べてかなり小さくなります。しかも電子メールで交換できますから,流通もしやすいと思われます。

 なお,小文で紹介したスクリプトにおけるパス名や行末符号の処理はMacOS用のMacPerlを前提にしています。MS-Windows用のJPerlでは扱いが少々異なりますので,ご注意下さい。

(1997年9月記)


(c)1998 JAGAT