Perlのアレコレ
bash と Perl はスクリプト言語(インタプリタで実行)としては同じですが、そのコンセプトには大きな違いがあります。bash と Perl には以下のようなコンセプトの違いがあります。
bash |
Perl |
様々なパーツ(コマンドとして実装されているもの)を組み合わせてひとつのものを作る |
Perl 自身で何でもできるように設計されている |
文字列に対する処理が貧弱(awk,sed (もちろん Perlも!)の力を借りないと何もできない) |
文字列に対する処理は既存の言語の中でも最強 |
実数演算ができない(整数のみ) |
ごく普通 |
bash 自体の拡張性がない(利用するコマンドの限界が bash としての限界) |
The Possibilities are infinite! |
結論からいえば、bash にできて Perl にできないことはありません
これは極論かもしれませんが、bash はファイル操作のみに重きをおいています。ですから、扱う単位がファイル単位です。つまり「このファイルの中に test という文字列があったら」という条件分岐ではなく、「このファイルのパーミッションがこうだったら」「このファイルの更新日時がこうだったら」のような扱いをすることになります。
bash における処理対象がこのようなものであることから、ファイルの中身をいじるようなプログラムは bash
単体では難しいということが分かってもらえると思います。
2. 文字列処理における最強の手段 〜〜〜 正規表現 〜〜〜
|
1章でも述べたように bash の主な使用目的はファイル単位の操作です。サンプルプログラムのほとんどもファイル操作です。
cut コマンドを使った CSV ファイルの処理は行いましたが、あくまでも ReadOnly でした。文字列の置換を行うような作業は bash ではできません。
「ファイルの中身をいじるかいじらないか」というのは、bash と Perl の使い分けの大事なポイントになります。
一般に人間が見たい、もしくは、見る必要があるファイルというのは文字列の集合です。つまりファイルをいじる = 文字列を処理する、ということを意味します。
前置きが長くなりましたが、文字列を処理する上で「正規表現」という特定の文字列に対してだけマッチするような、文字列表現の集大成があります。正規表現を一言でまとめるのは難しいですが、簡単な例を示すので感じだけ掴んでください。
2.1 正規表現のコア、「メタ文字」
ワイルドカードという言葉を聞いたことがあると思います。シェルで ls a* とすれば、a から始まるファイル名だけを表示する、というような使い方をしていると思います。メタ文字とはまさにこの * のことです。
正規表現における簡単なメタ文字を紹介します。
- . (ドット)
任意の1文字にマッチする。つまりどんな文字をも表すメタ文字です。
- *
直前のマッチの 0回以上の繰り返し。.* と書けば、どんな文字列でもマッチする。
- +
直前のマッチの 1回以上の繰り返し。
空文字に対して、「 .* 」はマッチするが、「 .+ 」はマッチしない。
このようなメタ文字を使って特定の文字列だけを入手することが出来します。流行りの XML などの処理にも正規表現は絶大な力を発揮します。
例えば、50MBもある XML ファイルから、<IMPORTANT> </IMPORTANT> の間の文字列だけを抜粋するような処理も、Perl と正規表現を使えば以下のように簡単にできます。
open FILE , 'target.xml';
@_ = ;
close FILE;
foreach (@_)
{
print $1 if /(.*?)<\/IMPORTANT>/g;
}
|
プログラム2.1
表示させるだけなら grep コマンドで出来ます。ここまでは実は bash でもがんばればできることです。
では、こんなのはどうでしょう。
歳を取ってきて、小さい字が読めなくなって来た。HTML の <FONT> タグで SIZE="+1" や SIZE="-3" となっているのを SIZE="+3" に全部変えたい。手作業でやれといわれたら泣きたくなるような作業です。bash でやろうと思っても、説明したように文字列の置換は bash ではできません。しかし、Perl ならこんなに簡単です。
open FILE , 'source.html';
@_ = ;
close FILE;
foreach (@_)
{
s/SIZE="(\+|-)\d"/SIZE="+3"/ig;
}
open FILE , '>convert.html';
print FILE @_;
close FILE;
|
プログラム2.2
SIZE="(\+|-)\d" の部分が正規表現です。
このように正規表現は、文字列中のパターンを独自のメタ文字とリテラル(メタ文字と違って、ある特定の一つの文字列にのみマッチする)によって表現することができます。
きっちりとマッチさせたい部分はリテラルで表記し、それ以外をメタ文字で表記します。上の例の場合、HTML ファイル中に「SIZE=」という文字は必ずある文字列です。その後に続く "+N" の部分はどうでもいいものです(元々していされているフォントサイズとは無関係に、すべて3にしたい)。
\d というのは数字にマッチするメタ文字です。つまり SIZE="+1" でも SIZE="+8" でも上の正規表現ならばマッチします。
SIZE="+3" の部分は置換後の文字列です。
s/// は、/ に囲まれた最初のフィールドに書かれた正規表現にマッチする部分を、次のフィールドに書かれている文字列置換する演算子です。仮に元の HTML に
test
と書かれていたとすると、SIZE="+1" の部分に正規表現がマッチし、その部分が SIZE="+3" に変わります。
これが Perl における 正規表現の使い方です。
2.2 ゴキブリの大群 ---エスケープ---
2.1 節で書いた正規表現をみれば分かるように、\ が2回登場しています。
プログラミングを経験したことがある人ならエスケープの意味が分かると思います。正規表現におけるエスケープは、メタ文字のパワーを奪いさる役目をします。. と書けば何にでもマッチする最強のメタ文字 . が \. と書かれた瞬間に「ただの . 」 になってしまいます。
Perl の正規表現中でエスケープしなければいけない文字は以下の通りです。
「 . 」 , 「 * 」 , 「 + 」 , 「 \」 , 「 ( 」 , 「 ) 」 , 「 [ 」 , 「 ] 」 , 「 { 」 , 「 } 」 , 「 / 」 , 「 ? 」 , 「 ^ 」 , 「 | 」 , 「 $ 」 , 「 @ 」
多いです。記号文字がほとんど対象になります。逆に、エスケープしなくてよい文字を覚えるほうが幸せになれるかもしれません。
Perl はその柔軟性から、C などのガチガチの言語に比べて比較的自由なコーディングスタイルをとることができます。
3.1 基本的な文法
Perl における文法は、C におけるそれと酷似しています。文の終わりはセミコロン、{} によるブロック構造など C プログラミングが分かる人ならば直感的に理解できるでしょう。
大きな違いはコメントです。C では /* */ でくくられている部分がコメントになりますが、Perl では(スクリプト言語という位置付けから)コメントは # で行います。# から行末までがコメントとみなされます。
print 1+1; #here is comment-out
となります。
"test" , 'test' はともに文字列 test を表しますが、"" 中では変数展開が行われ、'' 中では行われません。また、'' 中では、エスケープシーケンスも無視されます。
変数に関してはまだ触れていませんが、
$string = 'test';
print "$string"; # test が出力
print '$test'; # $test という文字列を出力
という扱いになります。
3.2 変数
Perl における変数は「無制限可変長変数」です。また型はありません。つまり数値でも文字列でもなんでも格納することが出来ます。
Perl では変数にアクセスする際、頭に $ をつけます。これは bash で変数を参照するときと同じです。
Perl は変数を宣言しなくても使えますが、タイプミスなどの無駄なバグの混入を防げるので、宣言することをおすすめします。
宣言していない変数の使用を防ぐには、Perl スクリプトの最初の方に
use strict;
と書くことで、Perlが自動的に変数の宣言のチェックを行ってくれます。
my $test
とかけば、test という名前の変数を使用することが出来ます。変数の取り扱いとしては他の言語と変わりありません。
#変数に値を代入
my $test;
$test = 1;
#文字列を代入
$test = "test";
Perl の変数に型がないのは、Perl 自身で変数の使い分けを行えるからです。関数や演算子の対象となっている変数の中身(具体的には数値か文字列か)を吟味し、その時々によって変数の内容を解釈してくれます。以下に具体例を示します。
my $string = "1.1";
my $num = 1.5;
print $string + $num;
結果は 2.6 が出力されます。文字列としての "1.1" と数値の 1.5 を + 演算子の対象にした場合、$string という変数に格納されている文字列を数値と解釈して $num に格納されている数値 1.5 と加算してその結果を返します。
3.3 配列
Perl での配列を理解する前に「リスト」という概念を理解しなければいけません。言葉で説明するのが難しいので以下に例を示します。
($var1 , $var2 , $var3) = (1 , 2 , 3);
このカッコでくくられたものがリストです。何とも表現しにくいですが、() でくくられ、カンマで区切られている値(変数)をグループ化したものだと考えてください。
上の例では、$var1 に 1 、$var2 に 2 、$var3 に 3 が代入されます。配列は $var1 、$var2 、 $var3 を要素番号で関連づけたものです。
Perl で配列を表す記号は @ です。@a は配列a を表します。配列を使用するには変数と同じく my で宣言してやります。
my @array;
@array = ("first" , "second" , "third");
print $array[0]; #first
print $array[1]; #second
print $array[2]; #third
上の例を見れば分かるように、配列の各要素にアクセスするには $配列名[要素番号]という書き方になります。配列の記号は @ なのに、要素を表すときには $ を使う、というのは分かりにくいですが、この記号の使い方は「$ はスカラーを表す」という Perl の記号仕様理念に基づいているものです。
複数個の変数を一つの配列にまとめたい場合も、リストの概念が分かっていれば簡単に出来ます。
my $arg1 , $arg2 , $arg3;
$arg1 = "first";
$arg2 = "second";
$arg3 = "third";
my @array;
@array = ($arg1 , $arg2 , $arg3);
配列同士のコピーも @to = @from とすることで、簡単に行えます。
3.4 ハッシュ
Perl でのハッシュは日本語で「連想配列」と訳されます。連想、というように、何かキーワードがあって、それに対応する値を格納するのがハッシュの役目です。ハッシュを表す記号は % です。%hash は hash という名前のハッシュを表します。
my %myself;
%myself = ('name'=> 'Ryo SHIMIZU' , 'age' => 21);
print $myself{name}; #Ryo SHIMIZU
print $myself{age}; #21
のような感じで使います。name というキーワードに対して 'Ryo SHIMIZU' という値があり、age というキーワードに対して 21 という値があります。
最初のうちは有効な使い方が分からないかもしれませんが、キーワードに関連づけて値を格納することができる、というのは直感的に分かりやすい部分があります。
ハッシュのキーワードのことを「キー」と呼びます。またキーに対応する値のことを「バリュー」と呼びます。
ハッシュのキーの部分を表記するのに変数を使うこともできます。
my %myself = ('name' => 'Ryo SHIMIZU' , 'age' => 21);
my $val1 = 'name';
my $val2 = 'age';
print $myself{$val1}; #Ryo SHIMIZU
print $myself{$val2}; #21
$myself{$val1} と書くことで、変数 $val1 が変数展開され文字列の name となります。よって、$myself{$val1} は $myself{'name'} と書いたのと同じことになります。
3.5 名前空間
Perl では、変数、配列、ハッシュの三つのタイプがありますが、それぞれの名前はすべて独立しています。つまり、
my $test
my @test
my %test
と、test とおいう名前の変数、配列、ハッシュを宣言することはエラーにはなりません。Perl がそれぞれの名前空間を用意しているため、たとえ変数と配列に同じ test という名前が付いていても、そこでバッティングが起こることはありません。
しかし、@test という配列の最初の要素を表すには $test[0] と書くので、人間にとって、変数としての $test と混同する恐れがあります。変数、配列、ハッシュのそれぞれに同じ名前をつけることは可能ですが、ソースの可読性を上げるならばそれをしない方がよいです。
4.1 演算子
Perl における演算子は C 言語とほぼ同じです。Perl 特有の演算子のみ説明します。
○数値に対する演算子
**
累乗演算子です。対象となる変数の累乗を返します。
== , !=
数値における等価演算子です。
○論理演算子
&& , ||
論理積、論理和です。and 、 or で書き換えが可能です。つまり
$arg1 && $arg2
は
$arg1 and $arg2
で書き換え可能です。
○文字列に対する演算子
eq , ne
文字列における、等価演算子です。
. (ドット)
文字列を連結する演算子です。$string = "a" . "b"; とした場合、$string には "ab" が格納されます。
x (アルファベットのエックス)
繰り返し演算子です。$string = "a" x 5; とした場合、$string には "aaaaa" が格納されます。
○その他の演算子
.. (ドットドット)
範囲演算子と呼ばれます。VBA の to と同じ役割をします。@array = (1 .. 5); とした場合、@array には 1 2 3 4 5 が格納されます。
=~ , !~
拘束演算子と呼ばれます。正規表現を使う際に用います。詳しくは文字列操作の章で説明します。
<>
ダイアモンド演算子と呼ばれます。ファイルハンドルを扱う際に用います。詳しくはファイル操作の章で説明します。
|
4.2 制御文
Perl における制御文は C とそう変わりはありません。しかし、foreach という特殊な制御文があるため、おそらく for 文を使う機会はあまりないでしょう。
- if 文
C の if 文とまったく同じです。基本構造は
if( 評価式 )
{
文
}
となります。C と異なる点は、{} を省略できないことです。C では {} 内が1文で終わる場合に限り {} を省略できますが、Perl ではそれはできません。
しかし代わりの書き方が用意されています。
$a++ if($a <= 10);
と書くことで、実質上 {} を省略することが出来ます。
また C での「else if」は、perl では「elsif」と書きます。
- unless 文
if 文と逆の動きをする制御文です。つまり評価式が偽のときに、{} 内を実行します
$test = 0;
unless($test == 1)
{
print '$test is not 1';
}
if(!($test == 1)) と書くのと同じ意味を持ちます。
- for 文
C とまったく同じです。
for(初期設定;実行条件;ループ最後の挙動)
{
文
}
ただし、break 文はありません。break 文に相当するのはlast文です。
- while 文
C と同じです。また for と同じく {} を省略できません。
- until 文
while 文と逆の動きをします。つまり評価式が偽のときにループを実行します。
- foreach 文
基本構文は
foreach $temp (@array)
{
式
}
です。
これは @array の値を順次 $temp に代入して処理を行います。以下に例を示します。
my @array = ( 1 , 2 , 3);
my $temp;
foreach $temp(@array)
{
print "$temp\n";
}
結果は
1
2
3
となります。foreach の1回目のループでは $temp に $array[0] が代入されています。同様に2回目のループでは $temp に $array[1] が代入されています。
配列はリストである、ということが分かっていれば以下の文が上の文と同じ意味を持つことが分かると思います。
my @array = ( 1 , 2 , 3);
my $temp;
foreach $temp(0 .. 2)
{
print "$array[$temp]\n";
}
これは foreach の一回目のループで $temp に 0 が代入されます。すなわち $array[$temp] は $array[0] を意味し、その値は 1 となります。
上の foreach 文の例を for 文を使って書き直すと以下のようになります。
my @array = ( 1 , 2 , 3);
my $temp;
my $i
for($i = 0;$i <= $#array;$i++)
{
$temp = $array[$i];
print "$temp\n";
$array[$i] = $temp;
}
$#array は配列 array の最後の要素番号を返す演算子です。
for 文のループの最後で、$array[$i] に $temp を代入しています。すなわち、foreach ループ中で、配列の要素を代入した $temp の値に変更を加えると、foreach を抜けたあと、配列 @array の値も変わります。以下に簡単な例を示します。
@array = (1 , 2 , 3);
my $temp;
foreach $temp (@array)
{
print "$temp\n";
}
foreach $temp (@array)
{
$temp++;
}
foreach $temp (@array)
{
print "$temp\n";
}
最初の foreach 文により、 1 2 3 が出力されます。2番目の foreach 文により、配列のそれぞれの要素をインクリメントしています。そして、最後の foreach 文でもう一度配列 @array の値を出力させています。ここでは 2 3 4 という出力が得られます。
このように、配列の値を展開した変数に変更を加えると、元の配列自体にも影響を及ぼす、ということを覚えておいてください。多くの場合、これは非常に便利です。
Perl には C でいう main 関数にあたるものが見た目上ありません。サブルーチン以外の部分はすべて仮想的に main 関数に対応づけられます。
この章では、Perl における関数(サブルーチン)の使い方と、それに伴う変数のスコープについて説明します。
5.1 関数の定義
Perl では以下のようにサブルーチンを定義、呼び出しします。
#main 関数
print "in the main\n";
&func;
sub func
{
print "in the func\n";
}
|
プログラム5.1
この実行結果は、
in the main
in the func
となります。サブルーチンを定義する際は sub FUNC_NAME{ ... } という構文になります。また、その関数を呼び出す際は、関数名の頭に & をつけることにより可能です。
Perl では関数呼び出しの際に () は必要ありません。もちろん () をつけてもきちんと動作します。() をつけるかどうかは、個人のコーディングスタイルによるものなので、どちらでも構いません。ただ、引数を取る関数の場合は、どこまでが引数かを明確にしてやるために、() をつける必要があります。
5.2 引数をとる関数の定義および変数のスコープ
通常、関数は引数をとって、その値をパラメータにして処理内容が変わってきます。C などの言語では、関数が引数を取る際、「仮引数」というものを定義しなければいけません。しかし強力な変数管理能力を持つ Perl にはそんなものは必要ありません。
サブルーチンが受け取る引数は、@_ という特殊な配列に格納されています。7章 にも書きましたが、@_ は特殊な配列で、スクリプト中においてグローバルで、どこからでも参照することができます。以下に引数を取るサブルーチンの例を示します。
#main 部分
my $arg1 = 1;
my $arg2 = 3;
my $recv_value = &plus($arg1, $arg2);
print $recv_value;
#main 終わり
sub plus
{
my $get1 = $_[0];
my $get2 = $_[1];
return($get1 + $get2);
}
|
プログラム5.2
さて、このサブルーチン plus が何をやっているかというと、二つの引数をとり、その二つの和を返しています。main 部分から渡された二つの引数は @_ に格納されています。二つの引数を格納する変数 $get1 , $get2 にはそれぞれ $_[0] , $_[1] を代入してやります。これが引数をとるサブルーチンの使い方です。
仮引数という存在がないおかげで、関数がとる引数を簡単に可変長にすることができます。引数をすべて受け取るための配列 @arg を用意してやって、@arg = @_; とすることで、引数すべてを @arg に格納することが出来ます。あとはすばらしい foreach 構文を使って処理をすれば完璧です。
引数を取る関数といっても、引数が @_ に格納されているという以外、なにも特殊なことはありません。また、より Perl らしい書き方をすれば、plus 関数の1行目は
my(get1 , get2) = @_;
のようになります。
plus 関数では $get1 , $get2 という二つの変数がありますが、この二つの変数は plus 関数の中でのみ有効なものです。
my で宣言した変数はレキシカル変数といい、変数として有効な範囲が明確に定められています。C++ でも
for(int i=0;i<10;i++)
{
printf("%d\n" , i);
}
のように、for 文のブロック中でのみ有効な変数 i を宣言することができますが、Perl での my 宣言はそれと同じ意味を持ちます。このことをより理解するための例を以下に示します。
#main 部分
my $test = 1;
print $test , "\n"; # 1が出力される
&hoge; # 2が出力される
print $test , "\n"; # 1が出力される
sub hoge
{
my $test = 2;
print $test , "\n";
}
|
プログラム5.3
main 関数と hoge 関数の両方で $test という変数を使っていますが、これら二つの変数は独立しています。よって、hoge 関数の中で $test の値を 2 にしても、それは main 関数での $test にはまったく関係ないのです。
最後に return 文の説明をします。Perl での return 文は何でも返すことが出来ます。C では return が返す値は一つしか許されません。よって構造体とポインタを駆使してなんとか希望するデータを関数から受け取っていましたが、Perl ではそんな悩みは無用です。
return の返す値としてリストを用いることで、いくつもの値を返すことができます。また、リストが返せるということは、もちろん配列も返せますし、ハッシュを返すこともできます。
上に書いた足し算の結果を返す関数 plus も、最後の return 文を以下のようにすることで、足し算の途中過程(何と何を足したか)を明確にすることができます。
return ($get1 , $get2 , $get1+get2);
Perl の強力な変数管理(メモリ管理)とリストという柔軟な型のおかげで、Perl でのサブルーチンというのはこのように扱いやすくなっています。
この章では正規表現および、正規表現を利用した文字列処理を説明していきます。
6.1 いかなる文字列であろうとマッチさせる!
2章で軽く触れたように、正規表現にはリテラルとメタ文字という2種類の道具を使って、文字列を表現します。リテラルとは自分自身にのみマッチするものです。メタ文字というのは、特定の文字(複数種類)にマッチするものです。メタ文字については軽く説明済みなので、感じだけはつかんでもらってると思います。
Perl では /test/ とかけばそれが正規表現になります。ここでは test が正規表現です。演算子の章で説明しましたが、正規表現がマッチしたかどうかは =~ 演算子を使って評価することが出来ます。Perl で正規表現を使う際に、どんな流れでコードを書くのかを分かってもらうために下にサンプルを示します。
$string = "this is test";
if($string =~ /test/)
{
print "match";
}
|
プログラム6.1
これが Perl での正規表現の基本の書き方です。$string には "this is test" という文字列が格納されています。その変数 $string に対して /test/ という正規表現を当てはめています。/test/ と表記された正規表現は、$string の中に "test" という文字列が含まれている、ということを意味します。この test がリテラルです。つまり、文字列としての "test" にしかマッチしません。
リテラルは厳密なマッチを行うために、標準で大文字小文字の区別をします。つまり /test/ は "Test" にはマッチしません。そこで、大文字小文字の区別をなくすには /test/i と書きます。最後のスラッシュの後に i (たぶん ignorecase) をつけることで、大文字小文字の区別をなくすことができ、/test/i は "Test" にも "tEsT" にもマッチします。
Perl での正規表現の使い方、リテラルの意味は理解できたと思います。では次にメタ文字について説明します。
2.1 節で 「 . 」 , 「 * 」 , 「 + 」 の3種類のメタ文字について紹介しました。* と + の使い分けが難しいところですが、通常は + を使っていれば問題はないでしょう。
. * + の他にもメタ文字は存在します。以下にメタ文字とその意味を列挙します。
メタ文字 | 意味 |
\d | 数字 |
\w | 英数字と下線 |
\n | 改行 |
\s | 空白文字(具体的には ホワイトスペース、タブ、改行) |
\t | タブ |
\d+ | 1文字以上の数字 |
\w+ | 単語 |
$ | 行の終わり |
これらのメタ文字を使って、特定の文字列パターンを表現することができます。
例えば、CSV ファイルで、以下のような物品の納入書があるとします。
3/15,プリンタ,2,29800yen(フォーマットが日付,品名,個数,金額となっている)
このフォーマットがきっちりと守られているかどうかを以下の正規表現でチェックすることができます。
$input = "3/15,printer,2,29800yen";
if($input =~ /\d+\/\d+,\w+,\d+,\d+yen/)
{
print "正しいフォーマット\n";
}
else
{
print "間違ったフォーマット\n";
}
|
プログラム6.2
この正規表現がちゃんと読めるでしょうか?
まず、最初のフィールドは日付なので 「数字/数字」 という文字列でなければいけません。数字にマッチするメタ文字は \d ですので、\d を使います。ただ、\d だと数字1文字にしかマッチしないので、これじゃダメです。よって、1回以上の繰り返しである + を使います。よって \d+ が数字列を表す正規表現であることが分かったと思います。
数字が / で区切られていることを表現するためには \d+/\d+ と書きます。でも、/ は Perl において、正規表現の終わりを示す文字として使っているので、このままではダメです。ゴキブリを思い出してください。/ はエスケープしてやらなければいけません。
これらのことをすべて踏まえた上で、数字/数字 という日付を正規表現で表すには
\d+\/\d+
と書くことが分かったと思います。
\d+\/\d+ の後のカンマはリテラルです。カンマはメタ文字ではないので、そのままカンマとして書きます。
次の品名のフィールドは簡単です。単語を表す \w+ を使ってやればいいだけです。また、カンマは単語を構成する文字ではないので、\w にはマッチしません。
次のフィールドも単純に数字のマッチです。ただ、数字が何文字来るか分からないので、\d+ と書きます。
最後のフィールドも、メタ文字の \d+ と リテラルの yen がくっついているだけです。
メタ文字とリテラルの組合せで、どんな文字列でも表現できることが分かったでしょうか。しかし、今のままでは ReadOnly です。
次は、マッチした文字列に対して、置換などの処理をすることについて説明します。
6.2 置換演算子
2.1 節の例で「SIZE="+1" になっているのを SIZE="+3" にする」という置換の作業を正規表現を使って行いました。文字列処理において、置換作業は重要なウェイトを占めてきます。
○ 置換演算子 s///
置換を行うために Perl には演算子が用意されています。この演算子の使い方は簡単で、
$target =~ s/置換する文字列の正規表現/置換語の文字列/;
という形になります。
$target という変数に "my name is shimizu" という文字列が入っているとき、shimizu を ryo に置換するには、
$target =~ s/shimizu/ryo/;
と記述するだけです。このように置換したい文字列がリテラルで書けるような場合は非常に楽ですが、それが正規表現を使わないと表現できないような文字列になったときに、その文字列パターンを表現できる正規表現をきっちりと書いてやらなければいけません。
○ 特殊変数 $N
特殊変数 $N(N=1,2,3...) は正規表現にマッチした部分の文字列が格納してある変数です。
$target = "my name is shimizu";
if($target =~ /my name is (\w+)/)
{
print $1;
}
このプログラムの結果は shimizu が出力されます。2行目の if 文の中の正規表現の中身を詳しく解説すると、 \w+ は単語にマッチするメタ表現です。そしてその前に my name is というリテラルがあります。ここで大切なのは \w+ を (\w+) と書くことです。メタ文字の部分を () でくくる事によって、Perl はそこのメタ文字でマッチした文字列(この場合は shimizu) を特殊変数 $1 に格納してくれます。
このように、my name is の後にくる単語を変数に格納できることによって、その変数の値によって条件分岐をおこなうことができます。
if($target =~ /my main editor is (\w+)/)
{
print "bad!\n" if $1 =~ /emacs/i;
print "good!\n" if $1 =~ /vi/i;
}
これは my main editor の次にくる単語が emacs(i オプションにより大文字小文字区別なし) だったら bad! と表示し、vi であれば good! と表示します。
特殊変数 $N の N の部分は、正規表現中での () にくくられた部分の数になります。
$target = "my name is shimizu";
$target =~ /my (\w+) is (\w+)/)
とすれば、$1 に "name" 、$2 に "shimizu" が格納されます。
この特殊変数を使うことで、2.1 節のプログラムに変化を加えることができます。
SIZE=N になっているところを SIZE=N+1 に変える
open FILE , 'yomitai.html';
@_ = <FILE>;
close FILE;
foreach $temp (@_)
{
if($temp =~ /SIZE="(\+\d+)"/)
{
$before_size = $after_size = $1;
$after_size++;
$temp =~ s/SIZE="\+$before_size"/SIZE="+$after_size"/g;
}
}
open FILE , '>dekaimojino.html';
print FILE @_;
close FILE;
|
プログラム6.3
このプログラムにより、SIZE="+1" となっているところを SIZE="+2" に、SIZE="+2" となっているところを SIZE="+3" に変えることが出来ます。
置換演算子の使い方は以上の通りです。置換演算子がどうこう、ではなく正規表現をいかに書けるか、ということが大切であることが理解できたと思います。
Perl はスクリプト言語という性質上、コードをより短く書けるような裏技が存在します。ルールは単純ですが、やっつけ仕事をするときには非常に重宝します。
$_ は宣言しなくても使えるグローバル変数です。ただ使えるというわけではなく、大抵の標準関数のデフォルトの引数に指定されています。通常、関数は引数をとりますが、Perl においては(変数を引数にとる関数で)引数を省略すると、$_ が引数に与えられたものとして動作します。
たとえば、
$_ = "test";
print;
とすれば、test という文字列が出力されます。また、関数だけでなく、正規表現のための =~ 演算子も $_ をデフォルトの引数対象として動作します。
$_ = "this is test";
if(/test/)
{
print "test is included\n";
}
とすれば /test/ で書いた正規表現のマッチ対象が $_ として if 文が評価されます。
さらに foreach 文で、配列の中身を展開する先も $_ がデフォルトになっています。
@test = (1 , 2 , 3);
foreach (@test)
{
print;
}
のような使い方もできます。これによってタイピング量を減らすことが出来ます。
@_ も $_ 同様、配列を引数にとる関数のデフォルトの引数として認識されます。一番よく使われるのが shift 関数です。shift 引数に与えられた配列の一番最初の要素を取り出し、配列の中身をシフトします。つまり今まで $_[1] だったものが $_[0] になります。@test という配列の一番最初の要素を取り出して、変数 $arg その値を格納するには、
$arg = shift(@test);
と書きます。
@_ は shift 関数のデフォルトの引数となっているので、
$arg = shift;
と書けば、@_ の最初の要素を $arg に格納することが出来ます。
shift 関数が最もよく使われるのは、サブルーチンでの引数の格納です。5.2 節で説明したように、Perl ではサブルーチンへの値の引渡しに @_ を使います。サブルーチン側で、引数を格納するための変数を用意し、@_ の中身をそこに格納するには以下のように行うことができます。
sub test
{
my $first = shift;
my $second = shift;
my $third = shift;
...
}
これらの例のように引数のデフォルトが $_ 、 @_ になっているものはたくさんあります。デフォルト引数を使うと、無駄なタイピングを減らすことができるので、より早くコードが書けるようになります。ただ、@_ はサブルーチンの値の受渡しに使われるので、むやみに使うと思いがけない動作をする可能性があるので注意してください。
今まで説明しなかったものを含めて、関数、演算子、特殊変数などの使い方を説明します。
- ファイルからの読み込み
open FILE_HANDLE , "FILE_NAME";
@ARRAY = <FILE_HANDLE>;
close FILE_HANDLE;
で、@ARRAY に FILE_NAME の内容がすべて読み込まます。FILE_HANDLE にはどんな名前でもつけられます。適宜分かりやすい名前をつけてください。
デフォルトの動作ではファイルの改行までをひとつの要素として扱います。つまり、
1
2
3
と書かれたファイルを @test に読み込んだとき、@test は $test[0] に 1 $test[1] に 2 $test[2] に 3 が格納されます。この動作を変えるには特殊変数 $/ の値を変えてやります。通常は $/ には \n が格納されています。これによって、改行を区切りにして要素を変えます。ここで
$/ = undef;
としてやることで、ファイルの中身をすべてひとつの変数に収めることが出来ます。
- ファイルへの書き込み
open FILE_HANDLE , ">FILE_NAME";
print FILE_HANDLE "test";
close FILE_HANDLE;
ファイル名の前に「 > 」をつけることにより、新規ファイルとして書き込みを行うことが出来ます。「 >> 」にすることで追記書き込みを行うことが出来ます。
print 関数でファイルハンドルを指定することにより、そこで指定された場所へ文字列を書き出すことができます。print 関数はファイルハンドルを指定しなければ、STDOUT(標準出力)に書き込むようになっています。
配列 @test を test.txt に書き込むには以下のようにします。
@test = ("my " , "name " ,"is " , "shimizu\n");
open FILE '>test.txt';
print FILE @test;
- シェル文の実行
Perl では system 関数、exec 関数 、`` 演算子により、シェル文を実行することが出来ます。
`` (バッククウォート)演算子は、`` でくくられた部分をシェル文として解釈し、実行した結果(シェルが標準出力に出力するもの)をリストとして返します。
chomp(my @ls = `ls`);
とすることで、カレントディレクトリのファイルを @ls に格納することが出来ます。
system 関数と exec 関数は、引数をシェル文として実行し、シェルが return したものを返します。通常の場合、シェル文が成功した場合は 0 が返ります。
シェル文を実行するという意味では system 関数と exec 関数は同じ動作ですが、exec 関数は実行された時点で Perl は終了します。つまり、
print "before exec\n";
exec "ls";
print "after exec\n";
のようなコードがあった場合、after exec は出力されません。
これらのシェル実行は便利な機能ですが、fork するオーバーヘッドが大きいのでなるべく使わないようにしたほうがいいです。
- スライス
Perl の [ ] 演算子は非常に強力です。[] 演算子はリスト(配列) が対象であれば、どんな場合でも動作します。
$second_ret = (&test)[1];
print $second_ret; #2 が出力される
sub test
{
return (1 , 2 , 3 , 4 , 5);
}
上記の例のように、5 つの要素を持つリストを返す test 関数の戻り値の 2 番目だけを $second_ret で受け取っています。つまり (&test)と書くことによって、test 関数の戻り値をリストとして解釈し、そのリストに対して要素番号を指定してやることで特定の値のみを取り出すことが出来ます。このような動作を「スライス」と呼びます。
要素番号に指定できるのは 1 つだけではありません。
@ret = (&test)[1,3];
と書けば、2 と 4 が @ret に格納されます。
- push 関数
push 関数は、
push @ARRAY , VALUE;
と書くことで、@ARRAY に VALUE を追加します。意味としては @ARRAY = (@ARRAY , VALUE) と書いたのと同じです。配列に値を逐次追加していくような処理ではかかせない関数です。
- 配列の要素数を調べる
Perl の配列が動的で柔軟なことは良く分かったと思います。しかし、動的すぎて、配列の要素数がいくつなのか分からなくなる場合もでてきます。そんなときは $# 演算子を使うことで、配列の要素数を簡単に調べることができます。
@test = (1 , 2 , 3);
print $#test;
実行結果は 2 になります。つまり $# 演算子が返すのは、対象となる配列の「最後の要素番号」です。Perl には foreach 文があるので、要素番号を使って配列にアクセスすることはあまりありませんが、複数の配列同士で同期を取るようなデータ構造の場合は要素番号でのアプローチが簡単です。
- split 関数
split 関数は、変数を対象として、正規表現によって指定されたデリミタ(区切り文字)を区切りに変数を分割し、リストとして返します。
$test = "my name is shimizu";
@ret = split(/\s/ , $test);
print $ret[1]; #name が出力される
カンマを区切り文字にしてやれば、CSV ファイルの処理も簡単に出来ます。test.csv が
Linux,Linus Torvalds,free
Windows,Bill Gates,expensive
Perl,Larry Wall,great
という内容だったとき、
open FILE , 'test.csv';
@file = <FILE>;
close FILE;
foreach (@file)
{
@name = (split(/,/)[1]; #split 関数のデフォルト引数も$_
}
というコードを実行すれば、@name に "Linus Torvalds" , "Bill Gates" , "Larry Wall" が格納されます。前述のスライスが分かっていれば、簡単に理解できると思います。
- コマンドライン引数
Perl スクリプトを実行するときにコマンドライン引数として渡された値は、@ARGV という配列に自動的に格納されています。使い方は普通の配列とまったく同じで、
my ($arg1 , $arg2 , $arg3) = @ARGV;
のように使えます。もちろん @ARGV のまま問題ありませんが、分かりやすいコードを書くために、ニーモニック的な名前をつけた変数に一度格納したほうがよいでしょう。
- 標準入力からの読み込み
標準入力からの読み込みは STDIN という特殊ファイルハンドルを使うことで出来ます。
$input = <STDIN>
これで、標準入力から1行、もしくは $/ で指定されている文字が入力されるまで読み込みます。
また、特定の文字が入力されるまで入力のループを回すには以下のようにします。
$stop = "end";
until($input eq $stop)
{
chomp($input = <STDIN>);
print $input;
}
- <> で括られた中身を取得する
<test>
/.*?<(.*?)>/
ここで、.*? と 最後に ? が付いているのは、最短マッチにするためです。? がないと、.* が > を飲み込んでしまい、行末まですべてマッチするようなメタ表現になってしまいます。
- () で括られた中身を取得する
(test)
/.*?\((.*?)\)/
( 、 ) のそれぞれをエスケープすることを忘れないでください。
- IP アドレスの形にマッチさせる
192.168.10.41
/\d*\.\d*\.\d*\.\d*/
- yyyymmdd の形の日付から日のみを得る
/\d{4}\d{2}(\d{2})/
{} は繰り返しの回数を指定するための正規表現です。
- yy/mm/dd の形の日付から日のみを得る
/\d{2}\/\d{2}\/(\d{2})/
/ をエスケープしましょう。
今まで説明に使って来たサンプル集は、多少非現実的な内容だったので、ここでは現実的なプログラムをサンプルとして紹介します。
Perl スクリプトの使い方は、hoge.pl というファイルに Perl の文を書き込み、
perl hoge.pl
として実行するか、hoge.pl の1行目に
#!/usr/bin/perl
と書いておいて、
./hoge.pl
として実行するかの二通りのやり方があります。どちらのやり方にも対応できるように、ここではスクリプトの1行目に必ず #!/usr/bin/perl を記述することにします。
- test.csv という CSV ファイルに何個のフィールドがあるか数える(csv_count.pl)
アプローチとしては、カンマの数を数え、その数+1 がフィールド数であるとします。
#!/usr/bin/perl
open FILE , "test.csv"; #test.csv を読み込みでオープン
$_ = ; #$_ にtest.csvの1行目の内容を格納
close FILE;
@_ = split /,/; #カンマで分割
my $count= $#_ + 1;
print "test.csv には $count 個のフィールドがあります。\n";
|
- grep コマンドを作ってみる(grep_emu.pl)
grep コマンドは 「 grep パターン ファイル名」とすることで、ファイルから正規表現パターンに一致する行を出力するプログラムです。
利便性を考えて、「複数のファイルを引数に取れる」、「マッチした行の行番号を出力する」という機能を持たせます。
#!/usr/bin/perl
exit if $#ARGV < 1; # @ARGV の要素数は最低でも 2
my $regex = shift @ARGV; #マッチに使う正規表現
my @files = @ARGV; #文字列を探すファイル(達)。
my $count = 1; #行番号
foreach $temp(@files) #ファイル単位でのループ
{
print "ファイル $temp でマッチしたもの\n"; #まずはファイル名を出力
open FILE , "$temp"; #ファイルオープン
@_ = ;
close FILE;
foreach (@_) #ファイルの中身を行単位でループ
{
if(/$regex/i) #マッチしたら
{
print "$count行目:" , $_ , "\n"; #行番号とその行の内容を出力
}
$count++; #行番号をインクリメント
}
$count = 1; #ひとつのファイルに対するループが終わるので行番号をリセット
}
|
このスクリプトを使うには
perl grep_emu hoge a.txt b.txt
とします。しかしバックスラッシュを含むメタ文字(\d など)を使うときは
perl grep_emu '\d+' a.txt b.txt
のように、正規表現の部分をシングルクウォートでくくってやらなければいけません。これは Perl の問題ではなく、シェルの問題です。
- C 言語のインデントを行う(indent.pl)
#!/usr/bin/perl
open FILE , "test.c";
@file = ;
close FILE;
my $tab = '';
my $level = 0;
my $tab = "\t" x $level;
foreach (0 .. $#file)
{
print $level , "\n";
if($file[$_] =~ /\{/)
{
$tab = "\t" x $level;
$file[$_] =~ s/^/$tab/;
$level++;
next;
}
elsif($file[$_] =~ /\}/)
{
$level--;
}
$tab = "\t" x $level;
$file[$_] =~ s/^/$tab/;
}
print @file; #インデントした C のソースを標準出力に出力する
|
- 漢数字をアラビア数字に変換する
my $str = "横浜ホテル二◯五号室";
my @kanji = ('◯' , '一' , '二' , '三' ,'四' ,'五' ,'六' ,'七' , '八' ,'九');
foreach(0 .. 9)
{
$str =~ s/$kanji[$_]/$_/g;
}
print $str; #横浜ホテル205号室
|