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

Lesson 2 データの読み込み,スクリプトファイルの書き方,特殊変数 ($/, $&, $' など)


■1. BEGINEND

スクリプトの実行開始時や終了時に(そしてその時のみに)処理を行う場合には,BEGIN, END を使って指定する。

BEGIN { 処理 } Perl がスクリプトが読み込むとき「処理」が実行される。オプション -n, -p を付けて実行されるスクリプトの場合でも,「処理」が実行されるのは一度だけ。
END { 処理 } Perl が終了する直前に一度だけ「処理」が実行される。オプション -n, -p を付けて実行されるスクリプトの場合でも,「処理」が実行されるのは一度だけ。

■2. 条件の指定:while

   while ( 条件 ) { 処理 }    または    処理 while ( 条件 )

「条件」が成立している間,「処理」を行う。(詳細)

   while ($var < 20) { $var = $var + 1; print "$var\n"; }
注意 「条件」部分で,比較演算子「==」と代入演算子「=」を書き間違えると無限ループに陥るので注意すること。「変数 $var の値が 20 である」のつもりで「$var = 20」と書いてしまうことは少なくないので気をつけた方がよい。無限ループに入ってしまったら,強制終了(cntrl + c)する。

while の条件部分にオプション g 付きの正規表現を書くと,該当する文字列が存在するだけマッチングを行うので,問題の文字列の出現回数を数えることができる。(if の場合との違いに注意。)

   while (/\band\b/g) { $count++; }

◇ スクリプトの実行

以下のスクリプトを実行して,結果を確認しなさい。

   1. % perl -e 'while ($var < 20){ $var = $var + 1; print "$var\n"; }'
   2. % perl -ne 'while (/\band\b/g) {$count++;} END {print "$count\n";}' dll.txt

■3. データの読み込み

ファイルからデータを読み込むには,ダイヤモンド演算子 <> を使う。オプション -n, -p を付けた場合と同じように,ファイル中のすべてのレコードを一つづつ読み込むには,while と組み合わせ,以下のようにする。(<> の間にスペースを入れないこと。)

   while (<>) { 処理 }

while の条件部分で <> を使うと,読み込んだレコードは自動的に $_ に代入される。

grep のようにパターンにマッチする行だけ抜き出すには,if と組み合わせて次のようにすればよい。

   while (<>) { if (/正規表現/) { print; } }

次のようにすれば,全ての行に対して同じ置換処理を行った結果が出力される。

   while (<>) { s/文字列/置換文字列/g; print; }

■4. レコードの単位の変更

特殊変数 $/ の値を変えることで,読み込むレコードの単位を変更することができる。$/ のデフォルトの値は "\n"。$/ の値を空の文字列 ("") とすると,空行をレコードの区切り文字 (record separator, RS) としてデータを読み込むことができる。

   #worth を含む「段落」を出力する
   % perl -e '$/ = ""; while (<>) {print if (/\bworth\b/i)}' dll.txt

$/ の値を未定義 (undef) にすると,ファイル全体が1レコードとなる。複数のファイルを指定すると,ファイル単位の処理をすることになる。

   % perl -e '$/ = undef; while (<>) {print if (/\bRussia/i)}' voa/* > result.txt

while ループに入る前にレコードの区切り文字を指定する点に注意する。

オプション -n, -p を使い自動でレコードを読み込ませるようにした場合でも,BEGIN を使えば $/ の単位を自由に変えられる。(注意)

   #ディレクトリ voa にあるファイルから 'Russia' を含むものを出力
   % perl -ne 'BEGIN {$/ = undef;} print if (/\bRussia/i);' voa/*
   #'Russia' を含むファイルの名前を出力
   % perl -ne 'BEGIN {$/ = undef;} print "$ARGV\n" if (/\bRussia/i);' voa/*

◇ スクリプトの実行

dll.txt から worth を含む「段落」を抜き出しなさい。


■5. スクリプトファイルでスクリプトを指定する

スクリプトの内容をファイルに書き,そのファイルを指定してスクリプトを実行することができる。スクリプトファイルには拡張子 .pl を付けることが多い。

   perl スクリプトファイル [入力ファイル] [> 出力ファイル]

スクリプトファイルの書き方

スクリプトをテキストファイルとして保存すればよい。

   % perl script/worth1.pl dll.txt

   script/worth1.pl
   -------------------------------------------------------
   $/ = ""; while(<>){ if (/\bworth\b/i) { print; } }
   -------------------------------------------------------

スペース,タブ,改行などは実行の際,無視されるので,見やすいように,スペースや改行などでレイアウトしてよい。また,# から改行までの部分は,実行の際,無視されるので,コメントなどを書き加える時に利用するとよい。

スクリプトは最後の処理が実行されると自動的に終了するが,明示的にスクリプトの終了を示すには「exit」を書く。

   % perl script/worth2.pl dll.txt

   script/worth2.pl
   --------------------------------------------------
   #レコード単位の指定
   $/ = "";

   #各レコードの処理
   while(<>){
      if (/\bworth\b/i) {
         print;
      }
   }

   exit;
   --------------------------------------------------
   % perl script/catlines.pl dll.txt | more

   script/catlines.pl
   --------------------------------------------------
   #「段落」中の改行を取るスクリプト

   $/ = "";               #空行をRSとする。

   while (<>) {           #「段落」単位で読み込んで,

      s/ *\n */ /g;       #改行を取り,
      s/^ +//;            #文字列先頭の不要なスペースを削除し,
      s/ +$//;            #文字列末の  〃 ,
      print "$_\n\n";     #空行付きで出力する。

   }

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

複数行からなる文字列を指定する方法 (ヒアドキュメント)
スクリプトファイルを実行可能ファイルにする方法
スクリプトのチェックの仕方


◇ スクリプトの実行

worth1.pl, catlines.pl の内容を cat で表示し確認した後,dll.txt を対象にコマンドラインから実行しなさい。


■6. ループ,レコードの処理の終了

exit を使えば,ある条件が成立したらスクリプトを終了することができる。

   if (条件) { exit; }     または      exit if (条件);

次の例のようにすれば,全てのレコードを処理せずにスクリプトを終了することができる。

   --------------------------------------------------
   #1〜20行を出力する
   while (<>) {
      exit if ($. == 21);
      print;
   }
   --------------------------------------------------

nextwhile の処理のブロックの中で使うと,スクリプトは終了せずに,next 以下の処理は行わず,続けて while の条件をチェックする。

   if (条件) { next; }     または  next if (条件);

次のスクリプトを実行すると,レコードが # で始まる場合,print を実行せずに次のレコードを読み込む。その結果,「# で始まる行を削除」したことになる。

   --------------------------------------------------
   while (<>) {
      next if (/^#/);
      print;
   }
   --------------------------------------------------

last を使えば,スクリプト自体は終了せずに,while のループを終了することができる。


          while
           │
    ┌─ 偽 <条件のチェック>←─┐  
 ス  │      真       
 ク  │      ↓       │
 リ←───── exit    next ───→
 プ  │    last         
 ト  │    │ │       │
 の  │    ↓ └───────┘
 終  └──────┐
 了         │
           ↓
         [次の処理]


■7. 特殊変数

次の変数を使うと,最後に正規表現にマッチした文字列の,様々な部分にアクセスすることができる。

$& 直前で正規表現にマッチした文字列全体
   % perl -pe 's/Japan[a-z]*/__$&__/ig;' voa/2-257714.txt
   % perl -pe 's/\bnot\W+to\b/<<$&>>/ig;' dll.txt
$n 正規表現に含まれる,n番目の ( ) で囲まれた部分にマッチした文字列。
   s/\b(after|before) (\S+ing)\b/<<$1>> [[$2]]/ig;
   #コンマ区切りデータの第1・第2フィールドを入れ換える (ファイルの内容)
   % perl -pe 's/^([^,]*),([^,]*)/$2,$1/' data/meibo1.txt
  [$1, $2, ... の値を一度に変数に代入する方法]
$+ 最後にマッチした正規表現の,一番右の ( ) にマッチした文字列。
   #行末の10文字を取りだす
   % perl -ne '/(.{0,10})$/; print "$+\n"' dll.txt

   [その他の例]
$` 正規表現にマッチした部分のの文字列。
$' 正規表現にマッチした部分の後ろの文字列。
   --------------------------------------------------
   $/ = "";

   while(<>){
      s/ *\n */ /g;
      s/ +$//;
      while(/\b(am|are|is)(n't| not)? [^ ]+ing\b/ig){
         print "$`\t$&\t$'\n";
      }
   }
   --------------------------------------------------
注意 上のスクリプトの while の条件にあるオプション g を書き忘れると,無限ループに入るので注意すること。

■8. 1行1文に整形する

言語研究では,テキストが1文1行に整形されていると便利なことが多い。これまでに学んできたことを利用すれば,かなりのところまで,自動で整形するスクリプトを書くことができる。([正規表現]

   % perl script/ll2ss.pl dll.txt | more

   script/ll2ss.pl
   --------------------------------------------------
   $/ = "";

   while (<>) {

      #処理対象以外の行は単に出力するだけ (注)
      if (/^#/) {
         print;
         next;
      }
   
      #段落中の行をつなぐ
      s/ *\n */ /g;

      #段落最後に挿入された余計なスペースを取る
      s/ +$//;

      #文の切れ目らしきところに改行を入れる
      s/([.?!]\W*) +(\W*[A-Z])/$1\n$2/g;

      #余分に改行を入れてしまったところを戻す
      s/(Mr|Mrs)\. *\n/$1\. /g;

      #空行付きで出力する
      print $_,"\n\n";
   
   }

   exit;
   --------------------------------------------------
処理対象外の「段落」の先頭には # が付いているものと仮定している。

□ 練習問題

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

  1. ディレクトリ voa にあるファイルから,日本関連の記事を抜きだしなさい。記事にファイル名を付ける,連番を振る,キーワードをマークするなど,出力の仕方を工夫すること。

  2. dll.txt を対象に,and, And, AND それぞれの出現回数を調べなさい。同様に,if, after などの接続詞についても出現回数を調べ,大文字小文字の違いによる出現回数の違いからどのようなことが言えるか考えなさい。

練習問題B:余裕があったらやってみよう。

  1. data/aesop.txt から,「うさぎと亀」の話を抜きだしなさい。

    ヒント1:話と話の間に空行2つが入っていることに着目する。

    ヒント2: 「うさぎに当たる英単語 (rabbit, hare)」と「亀に当たる英単語 (tortoise, turtle)」が両方出現しているデータを抜き出すには,次の2つの方法が考えられる。

    1. 一方を含むものを抜き出し,それを対象にもう一方が含まれているものを抜き出す。

    2. 両方が含まれていることを条件として指定して抜き出す。[複数の条件の指定方法について]

  2. ll2ss.pldll.txt 以外のテキストを対象に実行し,問題のある箇所をチェックしなさい。できるだけ問題が出ないようにスクリプトに改良しなさい。

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