本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはPerl入学式のサポーターで知られる尾形鉄次さんで、
本稿のサンプルコードは、use strict;とuse warnings;は省略しています。全文は、
正規表現は文字列処理の強い味方
Perlは文字列処理に強いと言われていますが、
一方で、
Perl正規表現の基本
正規表現の話題は広範です。紙幅の都合もあり、
| メタ文字 | 説明 |
|---|---|
| . | 任意の1文字にマッチする |
| [■] | 文字クラス。リスト■の中にある任意の1文字にマッチする |
| [^■] | 否定の文字クラス。リスト■の中にない任意の1文字にマッチする |
| ●? | 正規表現●が0個もしくは1個存在する |
| ●* | 正規表現●が0個もしくは1個以上存在する |
| ●+ | 正規表現●が1個以上存在する |
| ●*? | ●*と同じ意味だが、 |
| (●) | グループを作る。正規表現●にマッチした場合、 |
| (?:●) | グループを作るが、 |
| ●|◎ | 左右の正規表現●◎のどちらかにマッチする。上記のグループの中で使われることが多い |
| ^● | 正規表現●の冒頭にある場合、 |
| ●$ | 正規表現●の末尾にある場合、 |
| \s | 空白、 |
| \d | 数値1文字を表す |
| \b | 単語境界を表す |
表1に登場する用語を説明します。対象文字列が正規表現で表される場合、$1、に格納されます。
日本語文字列と正規表現
標準のPerlでは、
Perlで日本語の1文字を正しく認識する
日本語文字列を含むマルチバイト文字列をPerlで正しく扱うには、
ソースコード内に直接書かれた文字列リテラルを一括でPerlの内部文字列に変換するには、utf8プラグマを宣言します。
外部との入出力においてバイト列とPerlの内部文字列を相互変換するには、print関数などでの画面出力時にPerlIOを使用して自動的にバイト列への変換を行うbinmode STDOUT, 'utf8' 命令は、utf8プラグマ宣言時に便利です。
上記を怠った場合も、
# このスクリプトはUTF-8で書かれている
use utf8; ――(1)
binmode STDOUT, ':utf8'; ――(2)
my $greeting = "こんにちは"; ――(3)
my $len = length $greeting;
print "あいさつは「${greeting}」、文字数は${len}文字\n";utf8プラグマ宣言によって、"こんにちは"が5文字のPerlの内部文字列となり、$lenは5になります。binmode STDOUT, 'utf8'命令によって、
length関数は1バイトを1文字と解釈して、$lenは15となります
まとめると、
Perlの正規表現で日本語の1文字に正しくマッチする
バイト列とPerlの内部文字列の違いは、lengthだけでなく1文字を認識しようとするPerlでの指示全般に言えます。正規表現のメタ文字も例外ではなく、[■]が影響を受けます。
# このスクリプトはUTF-8で書かれている
# utf8プラグマ宣言なし。つまり文字列リテラルはバイト列
my $str = "ミーティングの日は木曜日です";
my ($week1) = $str =~ /(.)曜日/; ――(1)
my ($week2) = $str =~ /([月火水木金土日])曜日/; ――(2)
my ($week3) = $str =~ /(月|火|水|木|金|土|日)曜日/; ――(3)$week1と$week2は文字化けするのに対し、$week3は意図どおり"木"となります。
"木"が文字どおりの1文字ではなく3バイト分のUTF-8のバイト列"\xE6\x9C\xA8"として評価され、/(.)曜日/でキャプチャされるものは印字不可能な1バイト文字"\xA8"です。[月火水木金土日]の中には21バイトの文字があると評価されますが、"\xA8"がキャプチャされます。
(●|◎)には1文字を評価する意図がないためです。
上記の例においてutf8プラグマ宣言がある場合、"木"がキャプチャされます。
新規で書くPerlプログラムではutf8プラグマを宣言して、
正規表現にまつわる演算子
Perlには正規表現にまつわるさまざまな演算子があります。お馴染みかもしれませんが、
m//演算子──検索を行う
まずは、m//です。m//のmはマッチm//はマッチ演算子とも呼ばれます。
文字列が代入された変数$strに対し、●にマッチするパターンがあるかを検索したい場合、m//と二項演算子=~を使って$str=~ m/●/と書きます。マッチするパターンがある場合に$str =~ m/●/は真、if文の条件部分で使うことが多いです。
my $str = "ミーティングの日は木曜日です";
if ( $str =~ m/木曜日/ ) { # マッチする
print "Thursday\n"; # 表示される
}m//ではなくm||などのスラッシュ以外の区切り記号の選択については後述しますが、m記号を省略できるというルールがあります。上記の$str =~ m/木曜日/の場合、$str=~ /木曜日/とmを省略できます。
本稿でも、mを省略しますmが省略されている場合でも、m//と書いています。
s///演算子──置換を行う
マッチした箇所を別の文字列に置換する場合、 文字列変数 通常、 この よく使われる修飾子については後述します。 特殊変数 この直接書かれない 上記の例は、 また、 対となる記号がある括弧類を区切り記号にすることも可能で、 <続きの 2022年8月24日発売s///を使います。s///のsは代用を意味するsubstitutionの頭文字です。
$strと正規表現●があり、■で置換する場合、s///と=~を使って$str =~ s/●/■/と書きます。my $str = "ミーティングの日は木曜日です";
$str =~ s/木曜日/火曜日/; ――(1)
print "$str\n"; # => "ミーティングの日は火曜日です"s///と右側にgを付けます。gは大域を意味するglobalの頭文字です。my $str1 = "アリスさんとボブくん";
$str1 =~ s/君|くん|さん/さま/;
print "$str1\n"; # => アリスさまとボブくん
my $str2 = "太郎君と花子さん";
$str2 =~ s/君|くん|さん/さま/g; ――(1)
print "$str2\n"; # => "太郎さまと花子さま";gに代表される、/gのように書きます。特殊変数$_──正規表現演算子のデフォルトの処理対象
m//やs///が二項演算子=~を伴わない場合、$_とのマッチを試みます。つまり、m//やs///の左側に$_ =~が省略されたとみなされます。$_は、while(<>)構文で1行の文字列が格納される変数など、$_を活用して簡潔なプログラムを書く方針には賛否両論ありますが、while(<>) { ――(1)
next if /^\s*#/ || /^\s*$/; ――(2)
print; ――(3)
}#開始のコメント行を除いて表示します。while(<>)によって、$_に標準入力の1行が順番に代入されます。標準入力の末尾に到達すると、<>は偽と評価されてwhile文から抜けます。/\s*#/はコメント行、/^\s*$/は空行を表していて、m//は=~を伴っていないので、$_とマッチを試みます。printが$_を出力文字列とする性質を利用しています。区切り記号の変更
m//の解説時に少し触れましたが、m//、s///は区切り記号をスラッシュ/以外から選ぶことができます。while(<>) {
if ( m|(https?://\S+)| ) { ――(1)
print "$1\n";
}
}m//の区切り記号として縦線|を選んでいます。m/(https?:\/\/\S+)/のようにバックスラッシュを使ってエスケープする必要があります。正規表現で特定の記号群を多用する場合、s///の場合も、s|||のように変更できます。m{}、m[]、s{}{}、s[][]と直感的に書くことができます。波括弧{}は正規表現のメタ文字本誌最新号をチェック!
WEB+DB PRESS Vol.130
B5判/
定価1,628円
ISBN978-4-297-13000-8
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
いまはじめるFlutter
iOS/
作って学ぶWeb3
ブロックチェーン、

