Perl

Perlで正規表現(基本)

Perlでの正規表現を基本的なことからメモしていこうと思います。もともと正規表現はPerlで学習したこともあって書き方も楽で一番強力だと思っているんですが、どっちかというと「正規表現」という別のプログラミング言語を勉強するって感じなので、PHPとかにも応用できる部分は多々あります。

基本的な使い方

Perlでは正規表現はしばしば「パターン」と呼ばれます。文字列に対してパターンがマッチすれば(成功すれば)真を返し、マッチしなければ(失敗すれば)偽を返します。

パターンはスラッシュで囲んで指定します。この場合のスラッシュのように正規表現を囲む記号をデリミタと呼びます。

$_ = 'abcdefg';

if (/cde/) {
  print 'マッチしました';
}
else {
  print 'マッチしませんでした';
}

例では、条件判定のところにパターンだけ記述されていて文字列が何も指定されていません。この場合はデフォルト変数$_に対してcdeが含まれているかのマッチングを行います。その他の変数に対してマッチングを行うには結合演算子=~を使用します。

my $word = 'abcdefg';

if ($word =~ /cde/) {
  print 'マッチしました';
}
else {
  print 'マッチしませんでした';
}

Perlでは、正規表現を使うのに長ったらしい関数名をタイプする必要はありません。また、パターンマッチを行う際は!~を使うこともできます(※)!~はパターンがマッチすれば(成功すれば)偽を返し、マッチしなければ(失敗すれば)真を返します。
※ 結合演算子は置換などパターンマッチ以外にも使用されます。

my $word = 'abcdefg';

if ($word !~ /cde/) {
  print 'マッチしませんでした';
}
else {
  print 'マッチしました';
}

文字列を検索 〜 パターンマッチ演算子「m//

スラッシュでパターンを囲むのは実のところパターンマッチ演算子「m//」のショートカットです。mを明示することによって任意のデリミタを使えるようになります。デフォルトのスラッシュを使う場合にはいちいちmを書く必要はありません。

任意のデリミタは、よく使われるm{pattern}(ブレース)のように対になった記号や、m%pattern%など対にならないものが使えますが、もちろんパターンの中で使用しない記号を選ぶようにします。例えばURLのような書式は、デリミタがスラッシュのままではエスケープだらけで見にくくなってしまうので、あまりURLに登場しない記号をデリミタに使ったりします。

my $url = 'http://example.com/';

print ($url =~ m{^https://} ? 'HTTPSです' : 'HTTPSではありません');

Perlには他に似たような外見のqw//があります。

文字列を置換 〜 置換演算子「s///

文字列の置換には「s///」を使用します。置き換えの場合にも長ったらしい関数名をタイプする必要はありません。s/パターン/置換え文字列/のようにして使います。マッチに失敗した場合は変数の値は変わりません。

my $url = 'http://example.com/';
$url =~ s%^http://%https://%;
print $url;

> https://example.com/

m//と同じく任意のデリミタを指定することができます。デフォルトのスラッシュを使う場合でもsは必要です。ブレースのように対になったデリミタは、パターンを一度閉じるようにデリミタを4つ使って表記します。この単純な書き方に慣れるととても見やすいです。また、最初のパターンのデリミタと置き換え文字列のデリミタを違うカッコにすることもできます。

my $url = 'http://example.com/';
$url =~ s{^http://}(https://);
print $url;

Perlには他に似たような外見のtr///があります。

特殊文字 〜 メタキャラクタ

基本的な使い方の例にあるcdeのような固定の文字列にマッチさせるだけではあまり役に立たないので、正規表現では特殊な意味を持つメタキャラクタが用意されています。

\ | ( ) [ ] { } ^ $ * + ? .

メタキャラクタのひとつ、.(ドット)はワイルドカードと言って改行を除くあらゆる文字にマッチします。例えば/ha.d/というパターンはhandhardにマッチします。.(ドット)はきっかり1文字にマッチするのでhadharoldはマッチしません。

メタキャラクタのそのままの文字にマッチさせたければその文字の前に\(バックスラッシュ)を置きます。例えばexample.comにマッチさせるには/example\.com/とします。バックスラッシュの文字を表したければ\\と2個連続で記述します。

\(バックスラッシュ) 後ろのメタキャラクタを普通の文字にする。または次の英数字をメタキャラクタにする。
.(ドット) ワイルドカード。改行を除くあらゆる1文字にマッチ。
|(垂直バー) 選択肢。どちらか一方にマッチ。
(...)(丸括弧) 文字列のグループ化。カッコ内の文字列をひとつの文字として扱う。
[...](角括弧) 文字クラス。カッコ内の文字のどれか1つにマッチ。
^(キャレット) 文字列の先頭または行頭にマッチ。
$(ドル記号) 文字列の末尾または行末にマッチ。
use utf8;

my $word = 'パンダ、ゴリラ、ホッキョクグマ、オカピ';

if ( $word =~ /パ.ダ/ ) {
  print "'パ'と'ダ'の間に1文字見つかりました。\n";
}
if ( $word =~ /(パンダ|ゴリラ)/ ) {
  print "'パンダ'か'ゴリラ'が含まれています。\n";
}
if ( $word =~ /[パゴ]/ ) {
  print "'パ'と'ゴ'が見つかりました。\n";
}
if ( $word =~ /^パンダ/ ) {
  print "'パンダ'から始まっています。\n";
}
if ( $word =~ /オカピ$/ ) {
  print "'オカピ'で終わっています。\n";
}

量指定子

いくつ繰り返されているか分からないような文字にマッチさせるには、マッチさせたい文字の直後に量指定子を使います。

*(アスタリスク) 0個以上でマッチ。文字が無くてもマッチ。
+(プラス) 1個以上でマッチ。
?(クエスチョン) 0個または1個にマッチ。
{COUNT}(繰り返す数) きっかりCOUNTの回数繰り返す。
{MIN,}(最低回数,) MINの回数以上繰り返す。
{MIN,MAX}(最低回数,最高回数) MINの回数以上MAXの回数以下繰り返す。
my $word = 'パパパパパピププぺぺぺポポポポ';

if ( $word =~ /パ+/ ) {
  # $&には直前にマッチした文字列が入っています。
  print "$&\n";
}

> パパパパパ

量指定子は可能な限り多くの文字をマッチさせます。これを「最長マッチ」といいます。一方、出来る限り少ない文字でマッチさせることを「最短マッチ」といいます。最小マッチさせるには、最長マッチの量指定子の後に?を追加します。

*? 0個以上でマッチ。文字が無くてもマッチ(最短マッチ)。
+? 1個以上でマッチ(最短マッチ)。
?? 0個または1個にマッチ(最短マッチ)。
{MIN,}? MINの回数以上繰り返す(最短マッチ)。
{MIN,MAX}? MINの回数以上MAXの回数以下繰り返す(最短マッチ)。
my $word = 'パパパパパピププぺぺぺポポポポ';

if ( $word =~ /パ+?/ ) {
  print "$&\n";
}

> パ

いずれかの文字にマッチさせる 〜 文字クラス

ある文字のどれか1文字にマッチさせる場合は[](角括弧・ブラケット)の中にマッチさせたい文字を並べます。これを文字クラスと呼びます。

my $word = 'パパパパパピププぺぺぺポポポポ';

if ( $word =~ /[パポ]/ ) {
  print "'パ'か'ポ'が含まれています。";
}

文字クラスの中では、-(ハイフン)を使って文字の範囲を指定することができます。[a-z]とすればaからzまで、小文字アルファベットのどれかにマッチします。[a-zA-Z]は大文字小文字のアルファベット、[b-e]bからeまでのアルファベットどれかにマッチします。[0-9]0から9の数字にマッチします。

my $word = 'abcdefg';

if ( $word =~ /[a-z]/ ) {
  print "小文字のアルファベットが含まれています。";
}

場合によっては含まれない文字を探した方が早いこともあります。文字クラスの先頭に^(キャレット)を置くと、並べた文字以外の文字にマッチするようになります。入力フィールドに文字種の制限を設けるなどの場合にはよく使わます。

if ( $word =~ /[^a-zA-Z0-9]/ ) {
  print "半角英数字以外の文字が含まれています。";
}

よく使われる文字クラスにはショートカットが用意されています。改行を除くあらゆる文字を表す.(ドット)も特殊な文字クラスの一種です。

\s 空白文字。[ \t\n\r\f]と同じ。
\w 単語構成文字。[a-zA-Z0-9_]と同じ。
\d 数字。[0-9]と同じ。
\S 空白文字以外。[^\t\n\r\f]と同じ。
\W 単語構成文字以外。[^a-zA-Z0-9_]と同じ。
\D 数字以外。[^0-9]と同じ。

特殊な環境下では\s\dといった文字クラスが[ \t\n\r\f][0-9]と等価とは限らない場合があります。

if ( $word =~ /\D/ ) {
  print "半角数字以外の文字が含まれています。";
}

オプション修飾子 〜 フラグ

正規表現の動作をデフォルトから変更できる「オプション修飾子」が用意されています。単にオプションとかフラグと呼ばれることもあります。オプションは最後のデリミタの直後に指定します。複数の場合はまとめて指定することができます。

/i 大文字と小文字を区別せずにマッチする。
/m ^$が行頭や行末にマッチするようにする。
/s ワイルドカードの.(ドット)を改行にもマッチさせる。
/x パターンにある空白文字を無視してコメントを書けるようにする。
/o パターンのコンパイルを一回だけ行う。
/g グローバルマッチ。すべてのマッチを見つける。置換時はs///はすべて置き換える。
/e 置換時のみ。置き換え文字列を式として評価する。
my $word = <<EOT;
暑いので長袖を着ている。
寒いので半袖を着ている。
暑いか寒いか分からないので七分袖を着ている。
EOT

$word =~ s{
  (暑|寒)(い.+?)(長袖|半袖)
}{
  my $result = $1 . $2;
  if ( $1 eq '暑' && $3 eq '長袖' ) {
    $result .= '半袖';
  }
  elsif ( $1 eq '寒' && $3 eq '半袖' ) {
    $result .= '長袖';
  }
  else {
    $result .= $3;
  }
  $result;
}imxge;

print $word;