ここにあるドキュメントは古いものです。 より新しいものが別ディレクトリにあります → 5.8.8ベースのperlfaq6
perlfaq6 - Regexps ($Revision: 1.25 $, $Date: 1999/01/08 04:50:47 $)
この章は驚くほど小さくなっています。なぜならFAQの残りの部分は 正規表現を伴った回答と一緒にあちこちに散在しているからです。 たとえばURLをデコードするとかあるものが数値かどうか確認することは 正規表現を使って処理されますが、この回答はこの資料のあらゆる所で 見つけることができます(正確にはデータ操作とネットワーキングの章)。
どうすれば正規表現を判読し難い、保守できないようなものにすることなく 使うことができるでしょうか?
正規表現を保守可能なものにし、理解できるようにするための 三つの技法があります。
通常のPerlのコメントを使って、 あなたが何を、どのようにしているかを説明します。
# 行を、その最初の単語、コロン、行の残りの文字数に
# 変換します
s/^(\w+)(.*)/ lc($1) . ":" . length($2) /mge;
<CODE>/x</CODE> 修飾子は、正規表現中にある空白を無視するようにし、 (キャラクタクラスの中にあるものを除く)、通常のコメントが使えるように します。あなたの想像できるように、空白とコメントは非常に助けに なります。
/xによって
s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;
この正規表現を以下の様に記述できます:
s{ < # 開きのangle bracket
(?: # 後方参照なしのグルーピング
[^>'"] * # > でも ' でも "でもないものの0回以上の繰り返し
| # あるいは
".*?" # 二重引用符に囲まれたセクション (stingy match)
| # あるいは
'.*?' # 引用符に囲まれたセクション (stingy match)
) + # それらの一回以上の繰り返し
> # 閉じのangle bracket
}{}gsx; # 空に置き換え、つまり削除
訳注: stingy けちな、しみったれた; 少ない、不十分な
これでもまだ散文(prose)程には明確にはなっていませんが、 パターンの各部分の意味を説明するには非常に便利なものです。
私たちは通常、/で区切られたものをパターンであると考えています
が、パターンはほとんどすべてのキャラクタを使って区切ることが可能 です。perlreはこれを説明しています。たとえば、先に挙げたs///
では、区切りとしてカーリーブレースを使っています。スラッシュ以外
の区切りを選択することによって、パターンの中に存在する区切り記号
と同じものをクォートする手間を省くことができます。
s/\/usr\/local/\/usr\/share/g; # 良くない区切りの選択
s#/usr/local#/usr/share#g; # これは良い
二行以上に対するマッチングでトラブルがありました。何が悪いのでしょう?
マッチングの対象となっている文字列が実際には二行以上になっていないか、 パターンで正しい修飾子 (modifier)を使っていないかのいずれかでしょう (多分)。
複数行のデータを一つの文字列にする方法はたくさんあります。これを、
入力を読み込んでいる間自動で行なわせたいというのであれば、一度に
二行以上読ませるために $/を(パラグラフ単位で読み込みたいなら ''
を、ファイル全体を読み込みたいなら undefを)設定したくなるでし ょう。
あなたが使いたいのは /sか/mのいずれなのか(あるいはこれら両 方なのか)を決めるのを助けるために、perlreを読んでください:
/sはドットが改行を含むようにしますし、/mはキャレットとドル 記号が文字列の両端だけでなく改行の前後でマッチするようにします。
そして、複数行に渡る文字列を取得するようにさせる必要があります。
たとえば、以下に挙げるプログラムは重複した単語を、たとえそれが行
をまたがっていても(ただしパラグラフはまたがっていない)探し出すも
のです。この例では、/sの必要はありません。なぜなら、この行を
またがらせたい正規表現でドットを使っていないからです。/mを使 う必要もありません。それは、キャレットやドル記号をレコードの中に
ある改行の前後でマッチさせることは望んでいないからです。しかし、
$/をデフォルト以外のものに設定することは避けられませんし、そうし
なければ複数行レコードを読み込むことはできないのです。
$/ = ''; #一行ずつではなく、パラグラフ全体を読み込む
while ( <> ) {
while ( /\b([\w'-]+)(\s+\1)+\b/gi ) { # word starts alpha
print "Duplicate $1 at paragraph $.\n";
}
}
以下の例は、“From ”で始まるセンテンス(多くのメイラーによって変 形されるであろうもの)を検索するものです。
$/ = ''; #一行ずつではなく、パラグラフ全体を読み込む
while ( <> ) {
while ( /^From /gm ) { # /m によって ^ が \nの直後にマッチするようになる
print "leading from in paragraph $.\n";
}
}
次の例は、パラグラフ中のSTARTとENDに挟まれた部分を検索するもので す:
undef $/; # 一行とか一パラグラフではなくファイル全体を読み込む
while ( <> ) {
while ( /START(.*?)END/sm ) { # /s は . が行境界をまたぐようにします
print "$1\n";
}
}
異なる行にある二つのパターンに挟まれている行を取り出すのはどうやればできますか?
Perlの ..演算子を使えます(perlopに説明があります)。
perl -ne 'print if /START/ .. /END/' file1 file2 ...
行ではなく、テキストが必要なら次のようにします
perl -0777 -ne 'print "$1\n" while /START(.*?)END/gs' file1 file2 ...
しかし、STARTとENDが現れるのを入れ子にさせたいというのであ
れば、このセクションにある質問で説明されている問題に直面すること
になります。
..を使った別の例です:
while (<>) {
$in_header = 1 .. /^$/;
$in_body = /^$/ .. eof();
# now choose between them
} continue {
reset if eof(); # fix $.
}
正規表現を$/に設定したけど、駄目でした。何が悪かったのですか?
$/は正規表現ではなく、文字列でなければなりません。 この点に関してはawkの方が良いですね :-)
実際のには、ファイル全体をメモリーへ読み込んでしまうことを気にし ないのであればお望みのことを行うことができます。
undef $/;
@records = split /your_pattern/, <FH>;
Net::Telnet モジュール(CPANで入手可能)は、入力ストリームであるパ ターンを待ったり、それが特定の時間内に現れなかったときにはタイム アウトする機能を持っています。
## 三行からなるファイルを作成します。
open FH, ">file";
print FH "The first line\nThe second line\nThe third line\n";
close FH;
## それに対するread/writeファイルハンドルを取得します。
$fh = new FileHandle "+<file";
## それを“ストリーム”オブジェクトにアタッチします。
use Net::Telnet;
$file = new Net::Telnet (-fhopen => $fh);
## 二番目の行を探し、三番目の行を出力します。
$file->waitfor('/second line\n/');
print $file->getline;
演算子の左辺では大小文字を無視して、演算子の右辺では元の大小文字を保存しておくような 置換はどうやるの?
それは、あなたが“元の大小文字”(preserving case)をどのような意 味で使っているかによります。以下に挙げるスクリプトは、大小文字の 違いを保ったまま、文字毎に置換を行ないます。置換対象の文字列より も多くのキャラクターが置換後の文字列にあるのであれば、最後のキャ ラクターの大小文字の種別が置換後の文字列の残りの部分のキャラクタ ーに対して使われます。
# Original by Nathan Torkington, massaged by Jeffrey Friedl
#
sub preserve_case($$)
{
my ($old, $new) = @_;
my ($state) = 0; # 0 = no change; 1 = lc; 2 = uc
my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));
my ($len) = $oldlen < $newlen ? $oldlen : $newlen;
for ($i = 0; $i < $len; $i++) {
if ($c = substr($old, $i, 1), $c =~ /[\W\d_]/) {
$state = 0;
} elsif (lc $c eq $c) {
substr($new, $i, 1) = lc(substr($new, $i, 1));
$state = 1;
} else {
substr($new, $i, 1) = uc(substr($new, $i, 1));
$state = 2;
}
}
# 新しい文字列の残りの部分を仕上げる (newがoldより長い場合)
if ($newlen > $oldlen) {
if ($state == 1) {
substr($new, $oldlen) = lc(substr($new, $oldlen));
} elsif ($state == 2) {
substr($new, $oldlen) = uc(substr($new, $oldlen));
}
}
return $new;
}
$a = "this is a TEsT case";
$a =~ s/(test)/preserve_case($1, "success")/gie;
print "$a\n";
この出力は以下のようになります:
this is a SUcCESS case
\w がnational characterにマッチするように するにはどうすれば良いですか?
perllocaleを参照してください。
/[a-zA-Z]/の locae-smartなバージョンでマッチさせるには?
あなたの置かれているロカールに関りなく、alphabetic キャラクター は/[^\W\d_]/となります。非 alphabeticキャラクターは /[\W\d_]/
です(あなたがアンダースコアを文字と考えないと仮定しています)。
正規表現の中で使う変数をクォートするには?
Perlの構文解析器(parser)は、区切りがシングルクォーテーションでな
い限り、正規表現の中にある $variableや@variableといったものを展 開します。s/// による置換の右側にあるものはダブルクォーテーシ
ョンで括られた文字列とみなされるということを忘れないでください。
また、すべての正規表現演算子はその前に \Qを置いておかないと、正
規表現演算子として振る舞うということも忘れないでください。以下に
例を挙げます。
$string = "to die?";
$lhs = "die?";
$rhs = "sleep no more";
$string =~ s/\Q$lhs/$rhs/;
#ここで$stringは"to sleep no more"となる
\Qがないと、この正規表現は“di”にマッチします。
/oは実際なんのためのものなのですか?
正規表現マッチングで変数を使うと、そこを通る度に再評価(とおそら
くは再コンパイル)が強制的に発生します。/o修飾子は正規表現を最 初に使ったものにロックします。これは常に正規表現定数
(constant regular expression)に起きるもので、実際、パターンはプログラム全
体がコンパイルされたときと同時に内部表現にコンパイルされます。
/oの使用は、変数展開(variable interpolation)がパターンの中で
使われていなければ的外れなものになります。もし変数展開があると、
正規表現エンジンはパターンが非常に早い段階で評価された後で変
数が変更されたことを知ることもないし、気にかけることもありません。
/oは、変数の変更がないことがわかっていたり(なぜならあなた自身
が変数を変更しないことを知っているから)、変更されたことを正規表
現に通知したくないような場合に余計な評価を行なわないことによって
効率を上げるために良く使われます。
以下に挙げるのは “paragrep”プログラムです:
$/ = ''; # パラグラフモード
$pat = shift;
while (<>) {
print if /$pat/o;
}
ファイルから、C形式のコメントを剥ぎ取る(strip)するには どのように正規表現を使えば良いのでしょうか?
実際これは可能なのですが、あなたが考えているよりも非常に難しいも のです。たとえば次の一行野郎 (one-liner)はほとんどの場合にうまく 行きますが、すべての場合ではありません。
perl -0777 -pe 's{/\*.*?\*/}{}gs' foo.c
そう、これはCのプログラムを簡単に考えすぎているのです。特に、ク ォートされた文字列にコメントが出現するということを考慮していませ ん。このため、Jeffrey Friedlが作成した次の例のようなことが必要に なります。
$/ = undef;
$_ = <>;
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|\n+|.[^/"'\\]*)#$2#g;
print;
もちろんこれは、/x修飾子を使って空白やコメントを付加すること
で、より読みやすくすることが可能です。
Perlの正規表現をテキストのバランスが取れているかを 検査するために使えますか?
Perlの正規表現は、後方参照(\1など)のような便利な機能があるこ とで“数学的”
(mathematical)な正規表現よりも強力であるにもかか
わらず、この問題に対処するには能力が足りません。たとえば括弧やブ
レースに挟まれているテキストのようなもののバランスが取れているか
を解析するための、正規表現を使わないテクニックを使う必要がありま す。
ネストする可能性のある ` と ', { と }, ( と )
のような単一キャラクタのバランスを検査するための精巧なサブルーチ
ンが、http://www.perl.com/CPAN/authors/id/TOMC/scripts/pull_quotes.gz
にあります(7-bit ASCII専用)。
CPANにある C::Scanモジュールはこのようなサブルーチンを内部的に使 っているのですが、ドキュメントには載っていません。
正規表現が欲張り(greedy)であるとはどういうことですか?
ほとんどの人が、欲張り正規表現(greedy regexps)は可能な限りマッチ
すると考えています。技術的には、量指定子(?, *, +, {}) はパターン全体よりも貪欲です。Perlは local greedであることを好み、
全体の要求を即座に満足させます。同じ量指定子のnon-greedyバージョ
ンを得るには、??, *?, +?, {}?を使います。
例:
$s1 = $s2 = "I am very very cold";
$s1 =~ s/ve.*y //; # I am cold
$s2 =~ s/ve.*?y //; # I am very cold
二番目の置換が、“y ”を見つけてすぐにマッチングを中断しているこ
とに注目してください。量指定子 *?は正規表現エンジンに対して、
あなたが熱いジャガイモを扱っているときのように、可能な限り早くマ
ッチするのもを見つけて制御を次の行に渡すように効果的に指示します。
各行の、各単語毎に処理をするにはどうすれば良いですか?
split関数を使います。
while (<>) {
foreach $word ( split ) {
# $word に対する処理をここで行う
}
}
これは実際には英語でいうところの語ではないことに注意してください 。これは、単なる連続した空白でないキャラクターの塊です。
アルファベットもしくは数字の並びのみを対象とするには以下のように してできます。
while (<>) {
foreach $word (m/(\w+)/g) {
# ここで$wordに対する処理をする
}
}
語の出現頻度や行の出現頻度のまとめをどうやれば出力できますか?
これを行うためには、入力ストリームにある単語のそれぞれについて解 析する必要があります。私たちはここで、一つ前の質問と同様に、非空 白キャラクターの塊を語とするのではなくアルファベット、ハイフン、 アポストロフィ、の塊を語とします:
while (<>) {
while ( /(\b[^\W_\d][\w'-]+\b)/g ) { # misses "`sheep'"
$seen{$1}++;
}
}
while ( ($word, $count) = each %seen ) {
print "$count $word\n";
}
同じことを行に対して行ないたいのであれば、正規表現は必要ないでし ょう。
while (<>) {
$seen{$_}++;
}
while ( ($line, $count) = each %seen ) {
print "$count $line";
}
ソートされた順序で出力したいのなら、ハッシュのセクションを参照し てください。
曖昧なマッチング (approximate matching)はどうやればできますか?
CPANで入手できる String::Approx モジュールを参照してください。
たくさんの正規表現を一度に効率良くマッチングするには?
次のようなやり方は非常に効率が悪いものです:
# slow but obvious way
@popstates = qw(CO ON MI WI MN);
while (defined($line = <>)) {
for $state (@popstates) {
if ($line =~ /\b$state\b/i) {
print $line;
last;
}
なぜなら、Perlがそのようなパターンをファイルの各行毎に再コンパイル
しなければならないからです。5.005では、より良いやり方があり、
その一つは新たなqr//演算子を使うというものです。
# use spiffy new qr// operator, with /i flag even
use 5.005;
@popstates = qw(CO ON MI WI MN);
@poppats = map { qr/\b$_\b/i } @popstates;
while (defined($line = <>)) {
for $patobj (@poppats) {
print $line if $line =~ /$patobj/;
}
なぜ\bを使った語境界の検索がうまく行かないのでしょうか?
二つの良くある勘違いとは、\bをC<\s>と同じと考えてしまうという ことと、\bが空白キャラクターと非空白キャラクターの間にあるも
のと考えてしまうことです。これは両方とも間違いです。\bは\w
のキャラクターと、\Wのキャラクターとの間にマッチします(つまり、
\bは“語”の境界なのです)。これは^、$などのアンカーと同 じく幅がありません。ですから、これは何のキャラクターも消費しませ
ん。perlreでは、すべての正規表現メタキャラクターの振る舞いを 解説しています。
以下の例は、\bの間違った使い方と、それを直したものです。
"two words" =~ /(\w+)\b(\w+)/; # *間違い*
"two words" =~ /(\w+)\s+(\w+)/; # 正しい
" =matchless= text" =~ /\b=(\w+)=\b/; # *間違い*
" =matchless= text" =~ /=(\w+)=/; # 正しい
これらの演算子はあなたが思ったようには動作しないかもしれませんが、 それでも \bと\Bは実に便利に使えるのです。\bの正しい使い 方の例は、複数行に渡る重複単語のマッチングの例を見てください。
\Bを使った例は、\Bis\Bというものです。これは“this”や“island”
ではなく、“thistle”のように単語の中に収まっている“is”という
並びだけを見つけ出します。
なぜ $&, $`, $' を使うとプログラムが遅くなるのでしょうか?
プログラムのどこかでそういった変数が使われているのを見つけてしま うと、Perlはすべてのパターンマッチに対してそれに対処することをや らなければなりません。同様のからくりが、$1、$2などを使ったときに も行なわれます。このためすべての正規表現において、部分正規表現を 捕捉するために同じコストがかかることになります。しかし、スクリプ ト中で $&などを全く使っていないのであれば、正規表現は部分正規表 現を捕捉して不利になるようなことはしません。ですから、可能であれ ば $&や$'、$`を使わないようにすべきなのですが、それができないの であれば(一部のアルゴリズムはこれを使うのが便利なのです)、一度こ れらの変数を使ってしまったら好きなように使いましょう。なぜなら、 罰金はすでに払ってしまったのですから。アルゴリズムの中には こういった変数を使うことが適切であるものがあるということに注意して ください。リリース5.005では、$&はもはや“高価な”ものでは ありません。
正規表現の中で<CODE>\G</CODE> を使うと何が良いのですか?
\G記法は、最後にマッチしていた場所(つまり pos()の場所)がどこ
なのかを示すために正規表現につける目印で、/g修飾子と組み合わ せてマッチングや置換で使われます。/c修飾子が指定されていない 限り、失敗したマッチングは\Gの位置をリセットします。
例として、標準的なメイルやusenetのやり方で引用されているテキスト
(つまり、先頭に >がある)があって、先頭で連続している >
を同じ数の :に変換したいという状況を考えてみましょう。これは
以下のようにして実現できます。
s/^(>+)/':' x length($1)/gem;
これを、\Gを使ってより単純(かつ)高速にできます:
s/\G>/:/g;
より精巧な使い方は tokenizerに関連したものでしょう。以下に挙げた
lexに似た例は、Jeffrey Friedlの好意によるものです。これは 処理系 のバグのために
5.003では動作しませんが、5.004以降では動作します (/gを使ったマッチングが失敗して検索位置が文字列の先頭にリセッ
トされることを防ぐために、/cを使っていることに注意すること)。
while (<>) {
chomp;
PARSER: {
m/ \G( \d+\b )/gcx && do { print "number: $1\n"; redo; };
m/ \G( \w+ )/gcx && do { print "word: $1\n"; redo; };
m/ \G( \s+ )/gcx && do { print "space: $1\n"; redo; };
m/ \G( [^\w\d]+ )/gcx && do { print "other: $1\n"; redo; };
}
}
もちろん、以下のように書くこともできます
while (<>) {
chomp;
PARSER: {
if ( /\G( \d+\b )/gcx {
print "number: $1\n";
redo PARSER;
}
if ( /\G( \w+ )/gcx {
print "word: $1\n";
redo PARSER;
}
if ( /\G( \s+ )/gcx {
print "space: $1\n";
redo PARSER;
}
if ( /\G( [^\w\d]+ )/gcx {
print "other: $1\n";
redo PARSER;
}
}
}
しかし、これでは正規表現の垂直方向の揃えがなくなってしまいます。
Perlの正規表現ルーチンはDFAですかNFAですか? また、それはPOSIXに従ってますか?
Perlの正規表現はegrep(1)のDFA (deterministic finite automata, 決 定性有限オートマトン)と似たものではあるのですが、実際のところは バックトラックや後方参照 (backreferencing)のために NFAとして実装 されています。そして、Perlの正規表現は POSIX形式のものでもありま せん。なぜなら、それはすべてのケースにおいて最悪の振る舞いを行う からです(一部の人は、それが遅さをもたらすにもかからわず、一貫性 をもたらすという点を好んでいるようです)。上記のことなどに関して の詳細はJeffrery Friedlによる O'Reillyから出版されている ``Mastering Regular Expressions'' という本を参照してください。
voidコンテキストでgrepやmapを使うことのどこが間違っているのでしょうか?
grepとmapの両方とも、そのコンテキストには関係なくリストを返します。 これはつまり、Perlにあなたが無視してしまうための戻り値のリストを 作らせるということです。プログラミング言語を扱う方法はなく、 あなたは鈍感な無法者です!
複数バイトキャラクタを使った文字列のマッチングは どうすればできますか?
これは難しく、そしていい方法がありません。Perlは幅広文字 (wide characters)を直接はサポートしておらず、一バイトと一キャラクター とが同一であることを要求しています。以下に、The Perl Journalの 第五号でこの問題についてより詳しい記事を書いたJeffery Friedlに より提案されたアプローチの幾つかを挙げます。
さて、ここでASCIIの大文字二文字で火星語の符号化をしていると仮定 しましょう(たとえば、 ``CV'', ``SG'', ``VS'', ``XX''などといった二バイト の並びが火星語の一文字を表わすということです)。
ですから、火星語の符号化をしている 12バイトの ``I am CVSGXX!'' 文 字列は、'I', ' ', 'a', 'm', ' ', 'CV', 'SG', 'XX', '!' という九 文字で構成されます。
ここで、/GX/という一文字検索をしたいと考えてみましょう。Perl
は火星語については何も知りませんから、``I am CVSGXX!'' という文字 列にある
``GX''二バイトを見つけ出してしまうでしょうが、これは文字
としてそこにあるものではありません。つまり、``SG''に続けて``XX''があ
るのでそう見えるだけであって、本当に``GX''があるわけではないのです。
これは大きな問題です。
この問題に対処する方法が、うんざりするようなものですが、幾つかあ ります:
$martian =~ s/([A-Z][A-Z])/ $1 /g; #“火星語”のバイト並びが隣接しないよう
# にする
print "found GX!\n" if $martian =~ /GX/;
あるいは:
@chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
# 上の行は次のものと考えは同じ: @chars = $text =~ m/(.)/g;
#
foreach $char (@chars) {
print "found GX!\n", last if $char eq 'GX';
}
あるいは:
while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) { # \Gは多分不要
print "found GX!\n", last if $1 eq 'GX';
}
あるいは:
die "sorry, Perl doesn't (yet) have Martian support )-:\n";
(申し訳ない。Perlは(まだ)火星語をサポートしてません )-:)
今日一般的に使われている多くのダブルバイト(とマルチバイト)エンコーディング があります。
ユーザーによって与えられたパターンのマッチングはどうやるのですか?
Well, if it's really a pattern, then just use もしそれが本当にパターンであるのなら、単純に以下のようにできます。
chomp($pattern = <STDIN>);
if ($line =~ /$pattern/) { }
ユーザーが正しい正規表現を必ず入力するという保証がないのであれば、 以下のようにして例外を補足します。
if (eval { $line =~ /$pattern/ }) { }
もしパターンではなく文字列を検索したいというのであれば、
index()を使うとよいでしょう。これは文字列の検索のために
作られたものです。あるいは、パターンでないものにパターンのような
ものが入り込むのを防げないのであれば、perlreで説明されている
\Q...\Eを使いましょう。
$pattern = <STDIN>;
open (FILE, $input) or die "Couldn't open input $input: $!; aborting";
while (<FILE>) {
print if /\Q$pattern\E/;
}
close FILE;
Copyright (c) 1997-1999 Tom Christiansen and Nathan Torkington. All rights reserved. When included as part of the Standard Version of Perl, or as part of its complete documentation whether printed or otherwise, this work may be distributed only under the terms of Perl's Artistic License. Any distribution of this file or derivatives thereof outside of that package require that special arrangements be made with copyright holder.
Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.