[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]

Lesson 10 外部プログラム,ライブラリ,モジュールの利用


■1. 外部プログラムの利用

ここでは Perl のスクリプトから UNIX のコマンドや他のプログラムを利用する方法について見る。(UNIX のコマンドに依存したスクリプトは他のプラットフォームでは使えなくなるので注意が必要。)

・バッククォート (` `) と演算子 qx/ /

文字列を ` ` (あるいは演算子 qx/ / ) で括ると,文字列はコマンドライン上で指定されたコマンドと同様に実行され,標準出力への出力を返り値として返す。` ` で囲まれた文字列中の変数は展開されて実行される。

次の例であれば,コマンドラインから wc dll.txt を実行した結果,標準出力へ出力される文字列が,変数 $wc に代入される。

   $wc = `wc dll.txt`;

実行するプログラムはコマンドラインから実行可能なものであればよいので,例えば,茶筌 (日本語形態解析プログラム) が利用できる環境であれば,Perl のスクリプト内から茶筌を利用することができる。

   #neko.txt を茶筌で解析した結果を変数に代入
   $parsed = `chasen neko.txt`;

変数は展開されるので,次のように,引数を変数で指定しておけば,条件等によって処理対象,処理方法を変えることができる。

   $parsed = `chasen $option $file`;

system 関数

system 関数を用いても UNIX のコマンドを実行することができる。

   --------------------------------------------------
   $command = 'wc';
   $file = 'dll.txt';
   system "$command $file";  #'wc dll.txt' が実行される
   --------------------------------------------------

上のスクリプトを実行すると,標準出力に wc コマンドの実行結果が出力される。system 関数の返り値はプログラムの終了ステータスなので,= の右辺に置くと,左辺の変数には終了ステータスが代入される。` `, qx/ / のように処理結果が代入されるわけではないので,違いに注意すること。

・コマンドへの出力を開く

open 関数の2番目の引数の先頭にパイプ (|) があると,後続部分はコマンドと解釈され,ハンドルに対する出力はパイプを介してコマンドに送られる。(引数の数に注意。)

   --------------------------------------------------
   open (SORT, "| sort -t '|' -f +3");

   [中略]

   printf SORT "%5d | %s | %25s|%s|%-25.25s\n", 
               $count, $file, $pre, $key, $post;
   --------------------------------------------------

処理結果をさらに処理したい場合には,一度ファイルに保存し,そのファイルを開いてデータを読み込み処理すればよい。不要になったファイルは unlink で削除できる。


■2. ライブラリ

ある処理のために作成したサブルーチンを他のスクリプトでも利用したいことがある。サブルーチン部分をコピー&ペーストしてスクリプトに埋め込むのも一つの手であるが,ライブラリとして独立したファイルにまとめ,他のスクリプトから読み込んで利用できるようにすると便利である。ライブラリにして利用するようにすると,修正を行う場合でも1箇所で修正を行えばよく,スクリプトのメンテナンスも楽になる。(但し,あるスクリプトのために変更を加えると,他のスクリプトまで影響を受けることになるので注意すること。)

ライブラリの作成

ファイルにサブルーチン (複数可) を書き,最後に 1; を書く。(真の値を返せば1; でなくともよいが,1; を使うのが習慣。)

   --------------------------------------------------
   sub サブルーチン名1 {
       ... 処理 ...
   }

   sub サブルーチン名2 {
       ... 処理 ...
   }

   1;
   --------------------------------------------------

package 宣言

package を使って宣言すると,ブロック内の要素はその名前空間に属するものとして扱われ,同じ名前の要素があっても他の名前空間に属するものとは区別される。

   package パッケージ名

ライブラリの先頭で package 宣言しておけば,呼び出し元のスクリプトや他のライブラリに同名の変数やサブルーチンなどがあっても,パッケージ名 (名前空間) を付けることで他から区別することができるようになり,混乱が生じにくくなる。通常,パッケージ名は,ライブラリの名前から拡張子を取ったものが使われる (例えば,numeral.pl なら numeral)。

   numeral.pl
   --------------------------------------------------
   package numeral;

   sub card2ord {
       ... 処理 ...
   }

   1;
   --------------------------------------------------
                              [numeral.pl の内容を表示]

ライブラリ中の変数 $var は「$パッケージ名::var」で指定できる。サブルーチン &card2ord ならば「&パッケージ名::card2ord」となる。$, & などの記号が付く位置に注意。

メインプログラムは,明示的に宣言していなくとも,package main が宣言されているものとして処理される。パッケージ名が指定されていない変数等は,main パッケージに属するものとして扱われる。

ライブラリの読み込み

require ファイル名」でライブラリをスクリプトに読み込むことが出来る。サブルーチンは package 宣言していなければ,そのサブルーチン名を,package 宣言していれば,「パッケージ名::」を付けて指定する。numeral.pl (パッケージ名 numeral) の &card2ord であれば,「&numeral::card2ord」となる。(Perl4 では,:: の代りに ' を使っていた。)

   --------------------------------------------------
   require 'numeral.pl';

   foreach $cardinal (1 .. 100) {
      printf "%5s\n", &numeral::card2ord ($cardinal);
   }

   exit;
   --------------------------------------------------

ライブラリのパス

require の引数にパスなしのファイル名を指定すると,Perl は特殊配列 @INC に収められたパスを順番にチェックしていく。(@INC の内容は環境によって異なる。) 必要があれば,パスを付けて明示的にファイルを指定する。

   --------------------------------------------------
   % perl -e 'foreach $path (@INC) { print "$path\n"; }'

   /usr/lib/perl5/5.8.0/i386-linux
   /usr/lib/perl5/5.8.0
   /usr/lib/perl5/site_perl/5.8.0/i386-linux
   /usr/lib/perl5/site_perl/5.8.0
   /usr/lib/perl5/site_perl
   .
   %
   --------------------------------------------------

■3. 既存のライブラリの利用:jcode.pl

多くの人がライブラリを公開しているが,これらをうまく利用すれば,少ない労力で目的の処理が実現できる。ここでは,例として,jcode.pl を取り上げる。

jcode.pl:歌代和正氏による日本語コード変換のためのライブラリ。JIS, EUC, Shift JIS 間の相互変換が可能。(歌代和正氏 の jcode.pl のページ)

Perl 5.8 では Perl そのものが文字コード変換の機能を持っているが,以前のバージョンでは,Perl 自体は文字コード変換の機能を持っていなかったので,jcode.pl が日本語のテキストの処理に広く利用されていた。基本的な jcode.pl の使い方を理解しておくと,以前のバージョンのために書かれたスクリプトを読むときの参考になる。

ここではいくつか例を見るにとどめるのが,詳しく使い方を説明したページなどもあるので,必要であれば,そちらを見ること。例えば次のようなものがある。

「jcode.pl の私的な解説書」 (ミケネコ研究所)

文字コードの判定

getcode を使うと文字列 (以下,文字列が収められている変数を $line で表わす) の文字コードを判別することができる。文字コードが EUC なら "euc",JIS なら "jis",Shift JIS なら "sjis" を値として返す。

   getcode(\$line)
   --------------------------------------------------
   require 'jcode.pl';             #jcode.pl を読み込む
   $str = <>;                      #1レコード読み込む
   $code = &jcode::getcode(\$str); #文字コードを判別,値を $code に代入
   --------------------------------------------------
$str の前の \ を付け忘れないこと。\ の意味についてはここでは説明しない。

文字コードの変換

convert 関数を使うと,文字コードの変換ができる。入力文字コードを省略すると,文字コードを自動判別する。

   convert(\$line, 出力文字コード, 入力文字コード)
   --------------------------------------------------
   require 'jcode.pl';
   $str = <>;
   &jcode::convert(\$str, 'euc');
   --------------------------------------------------

   #Shift JIS のテキスト sjis.txt を EUC-JP に変換し出力
   % perl -pe 'require "jcode.pl"; &jcode::convert(\$_, "euc")' sjis.txt

tr は基本的に Perl の $line =~ tr/検索文字列/置換文字列/ と同じ働きをする。

   tr(\$line, 検索文字列, 置換文字列)
   tr(\$line, 'A-Z', 'a-z')    #大文字を小文字に変換

2バイト文字の範囲指定も可能。

   tr(\$line, 'A-Za-z', 'A-Za-z')

trans も同様の働きをするが,指定された文字列自体は置換されず,置換した結果を返り値として返す。($line\ は付けない。)

   trans($line, 検索文字列, 置換文字列)

◇ スクリプトの実行

jcode.pl を使って以下の作業をしなさい。

  1. 文字コードの判別: data/bottyan.euc の文字コードを確認しなさい。('euc' と出力されればOK。)

  2. 文字コードの変換: data/bottyan.euc を JIS に変換したファイル bottyan.jis を作成しなさい。
    cf. % nkf -j data/bottyan.euc > bottyan.jis

■4. モジュールの利用

自分で作るのは大変なので,ここでは使い方のみを見る。

モジュールを読み込む

use を使ってモジュールを読み込む。

   use モジュール名

「モジュール名」はファイル名から拡張子 .pm を除いたもの。モジュールは機能別にディレクトリにまとめられていることがあり,その場合,ディレクトリ名の後に :: を付けてファイルを指定する。

   Text::Wrap            #Text/Wrap.pm
   LWP::Protocol::http   #LWP/Protocol/http.pm

標準のモジュールを使う

Perl には標準で配付されているモジュールがある。(どのディレクトリにあるかは @INC の内容を調べればわかる。) 例として Text::Wrap の使い方について見ることにする。

マニュアルを見る

マニュアルが pod という形式でモジュール自身に埋め込まれている場合には,pod2man, pod2text というコマンドでマニュアルを作成することができる。pod2man の出力結果を整形して表示するには nroff コマンドを用いる。(各コマンドの使い方の詳細は man で確認すること。)

   -----------------------------------------------------------------
   % cd /usr/local/lib/perl5/5.8.0/Text
   % pod2man Wrap.pm | nroff -man | less



   User Contributed Perl Documentation                       Wrap(3)



   NAME
        Text::Wrap - line wrapping to form simple paragraphs

   SYNOPSIS
        Example 1

                use Text::Wrap

                $initial_tab = "\t";    # Tab before first line
                $subsequent_tab = "";   # All other lines flush left

                print wrap($initial_tab, $subsequent_tab, @text);
                print fill($initial_tab, $subsequent_tab, @text);

                @lines = wrap($initial_tab, $subsequent_tab, @text);

                @paragraphs = fill($initial_tab, $subsequent_tab, @text);

        Example 2

                use Text::Wrap qw(wrap $columns $huge);

                $columns = 132;         # Wrap at 132 characters
                $huge = 'die';
                $huge = 'wrap';
                $huge = 'overflow';

        Example 3

                use Text::Wrap

                $Text::Wrap::columns = 72;
                print wrap('', '', @text);

   DESCRIPTION
        "Text::Wrap::wrap()" is a very simple paragraph formatter.
        It formats a single paragraph at a time by breaking lines at
        word boundries.  Indentation is controlled for the first
        line ($initial_tab) and all subsquent lines
        ($subsequent_tab) independently.  Please note: $initial_tab
        and $subsequent_tab are the literal strings that will be
        used: it is unlikley you would want to pass in a number.

        Text::Wrap::fill() is a simple multi-paragraph formatter.
        It formats each paragraph separately and then joins them
        together when it's done.  It will destory any whitespace in
        the original text.  It breaks text into paragraphs by
        looking for whitespace after a newline.  In other respects
        it acts like wrap().

   〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
   -----------------------------------------------------------------

Text::Wrap を利用したテキストの整形

   デフォルトの長さで整形。第1段落のみ5文字字下げ。
   --------------------------------------------------
   use Text::Wrap;

   while (<>) {
      print wrap("     ", "", $_);
   }

   exit;
   --------------------------------------------------
   行の長さを50文字に指定して整形。
   --------------------------------------------------
   use Text::Wrap qw(wrap $columns);  #wrap の長さを指定する
   $columns = 50;                     #1行の長さは最大50文字

   while (<>) {
      print wrap("     ", "", $_);
   }

   exit;
   --------------------------------------------------

◇ スクリプトの実行

1. 次のスクリプトを実行して結果を確認しなさい。関数 wrap の第1・第2引数を変更し,意図通りの出力結果になることを確認しなさい。

   ----------------------------------------------------------------------
   % perl -ne 'BEGIN { use Text::Wrap; $/="" } s/ *\n */ /g; s/ +$//;
   print (wrap("     ", "", $_), "\n\n")' data/aesop.txt
   ----------------------------------------------------------------------
   % perl fmt_aesop.pl data/aesop.txt

   fmt_aesop.pl
   ----------------------------------------------------------------------
   use Text::Wrap;                           #Text/Wrap.pm を読み込む
   $/ = "";                                  #データは段落単位で読み込む

   while (<>) {
      s/ *\n */ /g;                          #段落内の改行を取り一行に
      s/ +$//;                               #段落最後の不要なスペースを取る
      print (wrap("     ", "", $_), "\n\n"); #wrap で整形,空行付きで出力
   }

   exit;
   ----------------------------------------------------------------------

2. 次のスクリプトを実行して結果を確認しなさい。$columns の値を変え,意図通りの出力結果になることを確認しなさい。

   ---------------------------------------------------------------------------
   % perl -ne 'BEGIN { use Text::Wrap qw(wrap $columns); $columns=50; $/="" }
   s/ *\n */ /g; s/ +$//; print (wrap("     ", "", $_), "\n\n")' data/aesop.txt
   ---------------------------------------------------------------------------

■6. Comprehensive Perl Archive Network (CPAN)

Comprehensive Perl Archive Network (CPAN) には多くのスクリプト,モジュールが登録されている。既存のもので処理ができるなら,それを利用する方が効率的である。

  CPAN の検索のページ:http://search.cpan.org/

□ 練習問題

練習問題A:必ずやること。

  1. 下のスクリプトはモジュール Text::Wrap を利用して,data/aesop.txt を整形するスクリプトである。スクリプトを実行し,結果がどうなるか確認した後,スクリプトの内容を確認しなさい。

       --------------------------------------------------
       use Text::Wrap qw(wrap $columns);
       $columns = 50;
    
       $/ = "\n\n\n";
    
       while (<>) {
          chomp;
          s/^\n+//;
          s/(\S) *\n *(\S)/$1 $2/g;
          $story = wrap("--\n\nTITLE: ", "", $_);
          $story =~ s/^ +//g;
          print "$story\n\n";
       }
    
       exit;
       --------------------------------------------------
    
  2. script/ll2ss.pl を参考にし,文字列を1文1行に整形するサブルーチン (ll2ss) を含むライブラリ (format.pl) を作成しなさい。(cf. Lesson 2 練習問題B2) 他のスクリプトから呼び出し,意図通りに動くか確認すること。

       ライブラリを呼び出すスクリプトの例
       --------------------------------------------------
       require 'format.pl';
       $/ = "";
       while (<>) { print &format::ll2ss($_), "\n"; }
       exit;
       --------------------------------------------------
    

[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]