\w
がローカルな文字セットにマッチするようにするには?/[a-zA-Z]/
の locale-smart なバージョンでマッチさせるには?/o
は実際なんのためのものなのですか?\b
を使った単語境界の検索がうまく行かないのでしょうか?\G
を使うと何が良いのですか?
perlfaq6 - 正規表現
この章は驚くほど小さくなっています。 なぜならFAQの残りの部分は 正規表現を伴った回答と一緒にあちこちに散在しているからです。 たとえばURLをデコードするとかあるものが数値かどうか確認することは 正規表現を使って処理されますが、この回答はこの資料のあらゆる所で 見つけることができます(正確には perlfaq9: "How do I decode or create those %-encodings on the web" と perlfaq4: "How do I determine whether a scalar is a number/whole/integer/float")。
正規表現を保守可能なものにし、理解できるようにするための 三つの技法があります。
通常のPerlのコメントを使って、 あなたが何を、どのようにしているかを説明します。
# turn the line into the first word, a colon, and the # number of characters on the rest of the line s/^(\w+)(.*)/ lc($1) . ":" . length($2) /meg;
<CODE>/x</CODE> 修飾子は、正規表現中にある空白を無視するようにし、 (キャラクタクラスの中にあるものを除く)、通常のコメントが使えるように します。 あなたの想像できるように、空白とコメントは非常に助けに なります。
/x
によって
s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;
この正規表現を以下の様に記述できます:
s{ < # opening angle bracket (?: # Non-backreffing grouping paren [^>'"] * # 0 or more things that are neither > nor ' nor " | # or else ".*?" # a section between double quotes (stingy match) | # or else '.*?' # a section between single quotes (stingy match) ) + # all occurring one or more times > # closing angle bracket }{}gsx; # replace with nothing, i.e. delete
これでもまだ散文(prose)程には明確にはなっていませんが、 パターンの各部分の意味を説明するには非常に便利なものです。
私たちは通常、/
で区切られたものをパターンであると考えていますが、
パターンはほとんどすべてのキャラクタを使って区切ることが可能です。
perlreはこれを説明しています。
たとえば、先に挙げたs///
では、区切りとしてカーリーブレースを使っています。
スラッシュ以外の区切りを選択することによって、パターンの中に存在する
区切り記号と同じものをクォートする手間を省くことができます。
s/\/usr\/local/\/usr\/share/g; # bad delimiter choice s#/usr/local#/usr/share#g; # better
マッチングの対象となっている文字列が実際には二行以上になっていないか、 パターンで正しい修飾子 (modifier)を使っていないかのいずれかでしょう (多分)。
複数行のデータを一つの文字列にする方法はたくさんあります。
これを、入力を読み込んでいる間自動で行なわせたいというのであれば、
一度に二行以上読ませるために $/を(パラグラフ単位で読み込みたいなら '' を、
ファイル全体を読み込みたいなら undef
を)設定したくなるでしょう。
あなたが使いたいのは /s
か /m
のいずれなのか(あるいはこれら
両方なのか)を決めるのを助けるために、perlre を読んでください:
/s
はドットが改行を含むようにしますし、/m
はキャレットとドル記号が
文字列の両端だけでなく改行の前後でマッチするようにします。
そして、複数行に渡る文字列を取得するようにさせる必要があります。
たとえば、以下に挙げるプログラムは重複した単語を、たとえそれが行を
またがっていても(ただしパラグラフはまたがっていない)探し出すものです。
この例では、/s
の必要はありません。
なぜなら、この行をまたがらせたい正規表現でドットを使っていないからです。
/m
を使う必要もありません。
それは、キャレットやドル記号をレコードの中にある改行の前後で
マッチさせることは望んでいないからです。
しかし、$/ をデフォルト以外のものに設定することは避けられませんし、
そうしなければ複数行レコードを読み込むことはできないのです。
$/ = ''; # read in whole paragraph, not just one line
while ( <> ) { while ( /\b([\w'-]+)(\s+\1)+\b/gi ) { # word starts alpha print "Duplicate $1 at paragraph $.\n"; } }
以下の例は、“From ”で始まるセンテンス(多くのメイラーによって 変形されるであろうもの)を検索するものです。
$/ = ''; # read in whole paragraph, not just one line while ( <> ) { while ( /^From /gm ) { # /m makes ^ match next to \n print "leading from in paragraph $.\n"; } }
次の例は、パラグラフ中の START と END に挟まれた部分を検索するものです:
undef $/; # read in whole file, not just one line or paragraph while ( <> ) { while ( /START(.*?)END/sgm ) { # /s makes . cross line boundaries 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 { $. = 0 if eof; # fix $. }
(brian d foy によって寄贈されました)
単に作業をすませたいだけなら、モジュールを使って、正規表現のことは
忘れてください。
XML::Parser
モジュールと HTML::Parser
モジュールはいい開始点ですが、
それぞれの名前空間は、特定の処理に特化したり、、それを行うための異なった方法の
ための他のパースモジュールを持ちます。
CPAN Search ( http://search.cpan.org ) から始めて、あなたのための作業を
既にしてくれている人がいないかどうかを探してみてください! :)
XML のようなものの問題点は、複数レベルのバランスされたテキストを含む
バランスされたテキストでありながら、ときどき空タグ
(例えば <br/>
)のようにバランスされていない
テキストがあることです。
それでも、予定外のことが起こります。
入力を処理するためのパターンが出来た、と思った瞬間、誰かが
あなたに変化球を投げてきます。
もしあなたが、正しい答えだけれども絶えず失望するような方法に道にひっかかれ、 バグ報告に包囲され、 3 角形の車輪を再開発するために費やす大量の時間に辟易とするような 厳しい道を望むなら、 フラストレーションで諦める前に試すことの出来ることがいくつかあります:
(?DEFINE)
機能を使った文法の定義を試す。
がんばって!
$/ は文字列でなければなりません。 もし本当にそうする必要があるなら、以下の例が使えます。
もし File::Stream があれば、簡単です。
use File::Stream;
my $stream = File::Stream->new( $filehandle, separator => qr/\s*,\s*/, );
print "$_\n" while <$stream>;
File::Stream がなければ、もう少し作業が必要です。
継続的にバッファに追加するために、sysread の 4 引数形式が使えます。 バッファに追加した後、(正規表現を使って)行全体が揃っているかを チェックします。
local $_ = ""; while( sysread FH, $_, 8192, length ) { while( s/^((?s).*?)your_pattern// ) { my $record = $1; # do stuff here. } }
もしファイル全体を最後までメモリに入れることを気にしないのなら、 foreach および、c フラグと \G アンカーを使ったマッチングを使って 同じことができます。
local $_ = ""; while( sysread FH, $_, 8192, length ) { foreach my $record ( m/\G((?s).*?)your_pattern/gc ) { # do stuff here. } substr( $_, 0, pos ) = "" if pos; }
以下に Larry Rosler によるとっても Perl ぽい解法があります。 これは ASCII 文字列に対するビット xor の動作を悪用します。
$_= "this is a TEsT case";
$old = 'test'; $new = 'success';
s{(\Q$old\E)} { uc $new | (uc $1 ^ $1) . (uc(substr $1, -1) ^ substr $1, -1) x (length($new) - length $1) }egi;
print;
以下はサブルーチンにしたものです。 上記と同じ手法です:
sub preserve_case($$) { my ($old, $new) = @_; my $mask = uc $old ^ $old;
uc $new | $mask . substr($mask, -1) x (length($new) - length($old)) }
$string = "this is a TEsT case"; $string =~ s/(test)/preserve_case($1, "success")/egi; print "$string\n";
これは以下のように表示します:
this is a SUcCESS case
代替案として、もし元のものの方が長い場合には置き換え単語の大文字小文字を 維持する場合は、Jeff Pinyan による以下のコードが使えます:
sub preserve_case { my ($from, $to) = @_; my ($lf, $lt) = map length, @_;
if ($lt < $lf) { $from = substr $from, 0, $lt } else { $from .= substr $to, $lf }
return uc $to | ($from ^ uc $from); }
これは文章を "this is a SUcCess case." に変更します。
もしもっと C っぽい解決法が好みなら、 以下に挙げるスクリプトは、大小文字の違いを保ったまま、 文字毎に置換を行ないます。 (そしてこれは Perl っぽい解法より 240% 遅いです。) 置換対象の文字列よりも多くの文字が置換後の文字列にあるのであれば、 最後の文字の大小文字の種別が置換後の文字列の残りの部分の 文字に対して使われます。
# 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; } } # finish up with any remaining new (for when new is longer than 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; }
\w
がローカルな文字セットにマッチするようにするには?use locale;
をスクリプトに書いてください。
\w 文字クラスが現在のロケールから取られるようになります。
詳しくは perllocale を参照してください。
/[a-zA-Z]/
の locale-smart なバージョンでマッチさせるには?perlre で記述されている、POSIX 文字クラス文法 /[[:alpha:]]/
が
使えます。
あなたがどこにいるかに関わらず、英文字は \w から数字と下線を除いたものです。
正規表現としては、これは /[^\W\d_]/
のようになります。
その逆である非英字は、\W に含まれる全ての文字に数字と下線を加えたもの、
つまり /[\W\d_]/
です。
Perlの構文解析器(parser)は、区切りがシングルクォーテーションでない限り、
正規表現の中にある $variable や @variable といったものを展開します。
s///
による置換の右側にあるものはダブルクォーテーションで
括られた文字列とみなされるということを忘れないでください。
また、すべての正規表現演算子はその前に \Q を置いておかないと、
正規表現演算子として振る舞うということも忘れないでください。
以下に例を挙げます。
$string = "Placido P. Octopus"; $regex = "P.";
$string =~ s/$regex/Polyp/; # $string is now "Polypacido P. Octopus"
.
正規表現では特別で、任意の 1 文字にマッチングするので、
ここでの P.
は元の文字列の <Pl> にマッチングします。
.
の特殊な意味をエスケープするには、\Q
を使います:
$string = "Placido P. Octopus"; $regex = "P.";
$string =~ s/\Q$regex/Polyp/; # $string is now "Placido Polyp Octopus"
\Q
を使うことによって、正規表現中の .
は通常の文字として扱われるので、
P.
は P
の後にピリオドがあるものにマッチングします。
/o
は実際なんのためのものなのですか?(brian d foy によって寄贈されました)
正規表現の /o
オプション (perlop と perlreref で文書化されています)
は、正規表現を一度だけコンパイルするように Perl に伝えます。
これはパターンに変数が含まれている場合にのみ有用です。
Perls 5.6 以降では、パターンが変わらない場合はこれを自動的に扱います。
マッチング演算子 m//
, 置換演算子 s///
, 正規表現クォート演算子
qr//
はダブルクォート風構造なので、パターン中で変数を展開できます。
詳細については "How can I quote a variable to use in a regex?" の答えを
参照してください。
この例は正規表現を引数リストから取って、それにマッチングする入力行を 表示します:
my $pattern = shift @ARGV;
while( <> ) { print if m/$pattern/; }
バージョン 5.6 より前の Perl では、$pattern
に変更がなくても反復毎に
正規表現が再コンパイルされます。
/o
をつけると、パターンを初回にコンパイルし、引き続く反復では
再利用するように Perl に伝えることで、再コンパイルを防ぎます:
my $pattern = shift @ARGV;
while( <> ) { print if m/$pattern/o; # useful for Perl < 5.6 }
バージョン 5.6 以降では、変数が変更されていない場合は Perl は正規表現を
再コンパイルしませんので、おそらく /o
は不要です。
害はもたらしませんが、助けにもなりません。
どのバージョンでも、たとえ変数の値が変わっても正規表現を一度だけ
コンパイルするようにしたい場合は、未だに /o
が必要です。
Perl が正規表現を再コンパイルしたときに自分自身で検証するために、動作中の
Perl の正規表現エンジンを見守ることもできます。
use re 'debug'
プラグマ (Perl 5.005 以降で実装されています) は詳細を
表示します。
5.6 より前の Perl では、各反復で正規表現がコンパイルされる毎に re
の
報告を見ることになります。
Perl 5.6 以降では、最初の反復でのみ re
の報告を見ることになります。
use re 'debug';
$regex = 'Perl'; foreach ( qw(Perl Java Ruby Python) ) { print STDERR "-" x 73, "\n"; print STDERR "Trying $_...\n"; print STDERR "\t$_ is good!\n" if m/$regex/; }
実際これは可能なのですが、あなたが考えているよりも非常に難しいものです。 たとえば次の一行野郎 (one-liner) はほとんどの場合にうまく行きますが、 すべての場合ではありません。
perl -0777 -pe 's{/\*.*?\*/}{}gs' foo.c
そう、これは C のプログラムを簡単に考えすぎているのです。 特に、クォートされた文字列にコメントが出現するということを考慮していません。 このため、Jeffrey Friedl が作成し、後に Fred Curtis によって修正された 次の例のようなことが必要になります。
$/ = undef; $_ = <>; s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse; print;
もちろんこれは、/x
修飾子を使って空白やコメントを付加することで、
より読みやすくすることが可能です。
以下は Fred Curtis の提供による拡張版です。
s{ /\* ## Start of /* ... */ comment [^*]*\*+ ## Non-* followed by 1-or-more *'s ( [^/*][^*]*\*+ )* ## 0-or-more things which don't start with / ## but do end with '*' / ## End of /* ... */ comment
| ## OR various things which aren't comments:
( " ## Start of " ... " string ( \\. ## Escaped char | ## OR [^"\\] ## Non "\ )* " ## End of " ... " string
| ## OR
' ## Start of ' ... ' string ( \\. ## Escaped char | ## OR [^'\\] ## Non '\ )* ' ## End of ' ... ' string
| ## OR
. ## Anything other char [^/"'\\]* ## Chars which doesn't start a comment, string or escape ) }{defined $2 ? $2 : ""}gxse;
(継続文字を使って複数行にわたっていない限りは) 少しの変更で、おそらくは継続文字を使った複数行にわたるものも含む、 C++ コメントも除去します:
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $3 ? $3 : ""#gse;
(brian d foy によって寄贈されました)
おそらく、まずはじめに試すべきなのは、Perl 5.8 から Perl 標準ライブラリに
なっている Text::Balanced
モジュールです。
これには扱いにくい文章を扱うための様々な関数があります。
Regexp::Common
モジュールも、すぐ使えるパターンを提供することで
手助けしてくれます。
Perl 5.10 以降では、再帰パターンを使って正規表現でバランスされた
テキストをマッチングすることが出来ます。
Perl 5.10 以前では、(??{})
シーケンス内で Perl コードを使うといった
さまざまな技を使う必要があります。
以下は再帰的正規表現を使った例です。 ゴールは、(ネストしたものを含む)山かっこ内の全てのテキストを 捕らえることです。 サンプルの文章には二つの「主な」グループがあります: 1 レベルの ネストがあるものと、2 レベルのネストがあるものです。 これは全部で五つの山かっこグループがあります:
I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it.
バランスされたテキストにマッチングする正規表現は二つの新しい (Perl 5.10 以降の)正規表現の機能を使います。 これらは perlre で説明されていて、この例はその文書にあるものの 修正版です。
まず、絶対量指定子を追加することで、
最大マッチングを行って、バックトラックをしません。
これは、山かっこをバックトラッキングではなく再帰で扱いたいので
重要です。
グループ [^<>]++
は、一つまたは複数の、山かっこでないものを
バックトラッキングなしで探し出します。
次に、新しい (?PARNO)
は、PARNO
で与えられる特定の捕捉バッファ内の
副パターンを参照します。
以下の正規表現では、最初の捕捉バッファがバランスされたテキストを
発見し(さらに保存し)、ネストしたテキストを調べるために最初のバッファ内に
同じパターンを適用する必要があります。
(?1)
は、外側の捕捉バッファのパターンを正規表現内の
独立した部分として使います。
これを全てまとめると:
#!/usr/local/bin/perl5.10.0
my $string =<<"HERE"; I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it. HERE
my @groups = $string =~ m/ ( # start of capture buffer 1 < # match an opening angle bracket (?: [^<>]++ # one or more non angle brackets, non backtracking | (?1) # found < or >, so recurse to capture buffer 1 )* > # match a closing angle bracket ) # end of capture buffer 1 /xg;
$" = "\n\t"; print "Found:\n\t@groups\n";
Perl が二つの主なグループを発見したことが出力されます:
Found: <brackets in <nested brackets> > <another group <nested once <nested twice> > >
もう少し作業すれば、たとえ他の山かっこがあっても、全ての山かっこの グループを得られます。 バランスされたマッチングが得られる毎に、外側のデリミタを取り除いて (これは今マッチングしたモノにまたマッチングしないようにするためです)、 処理する文字列のキューに追加します。 これを、マッチングしなくなるまで繰り返します:
#!/usr/local/bin/perl5.10.0
my @queue =<<"HERE"; I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it. HERE
my $regex = qr/ ( # start of bracket 1 < # match an opening angle bracket (?: [^<>]++ # one or more non angle brackets, non backtracking | (?1) # recurse to bracket 1 )* > # match a closing angle bracket ) # end of bracket 1 /x;
$" = "\n\t";
while( @queue ) { my $string = shift @queue;
my @groups = $string =~ m/$regex/g; print "Found:\n\t@groups\n\n" if @groups;
unshift @queue, map { s/^<//; s/>$//; $_ } @groups; }
全てのグループが出力されます。 一番外側のマッチングが最初に表示され、ネストしたマッチングはその後に なります:
Found: <brackets in <nested brackets> > <another group <nested once <nested twice> > >
Found: <nested brackets>
Found: <nested once <nested twice> >
Found: <nested twice>
ほとんどの人が、貪欲正規表現(greedy regexps)は可能な限りマッチすると
考えています。
技術的には、量指定子(?
, *
, +
, {}
) はパターン全体よりも貪欲です。
Perl は、全体的な貪欲性よりも局所貪欲性と即時の満足性を好みます。
同じ量指定子の 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 ) { # do something with $word here } }
これは実際には英語でいうところの語ではないことに注意してください。 これは、単なる連続した空白でない文字の塊です。
アルファベットもしくは数字の並びのみを対象とするには以下のようにして できます。
while (<>) { foreach $word (m/(\w+)/g) { # do something with $word here } }
これを行うためには、入力ストリームにある単語のそれぞれについて解析する 必要があります。 私たちはここで、一つ前の質問と同様に、非空白文字の塊を語と するのではなく、英字、ハイフン、アポストロフィ、の塊を語とします:
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"; }
ソートされた順序で出力したいのなら、perlfaq4: "How do I sort a hash (optionally by value instead of key)?" を参照してください。
CPAN で入手できる String::Approx モジュールを参照してください。
(brian d foy によって寄贈されました)
マッチングを行う毎に Perl が正規表現をコンパイルすることを避けてください。
この例の場合、$pattern が何者なのかを知る方法がないので、perl は foreach
ループの反復毎に正規表現を再コンパイルしなければなりません。
@patterns = qw( foo bar baz );
LINE: while( <DATA> ) { foreach $pattern ( @patterns ) { if( /\b$pattern\b/i ) { print; next LINE; } } }
qr//
演算子は perl 5.005 で現れました。
これは正規表現をコンパイルしますが、適用はしません。
プリコンパイルされた正規表現を使うと、perl の作業は減ります。
この例では、各パターンをプリコンパイルされた形式に変換するための
map()
を挿入しています。
スクリプトの残りの部分は同じですが、より速いです。
@patterns = map { qr/\b$_\b/i } qw( foo bar baz );
LINE: while( <> ) { foreach $pattern ( @patterns ) { if( /$pattern/ ) { print; next LINE; } } }
いくつかの場合では、複数のパターンを一つの正規表現にできるかもしれません。 しかし、バックトラッキングが必要になる状況に注意してください。
$regex = join '|', qw( foo bar baz );
LINE: while( <> ) { print if /\b(?:$regex)\b/i; }
正規表現の効率に関するさらなる詳細については、Jeffrey Freidl による Mastering Regular Expressions を参照してください。 彼は、どのように正規表現エンジンが動作するかと、なぜある種のパターンが 驚くほど非効率かについて説明しています。 perl がどのように正規表現を適用するかを理解すれば、これらを個々の状況に 調整できます。
\b
を使った単語境界の検索がうまく行かないのでしょうか?(brian d foy によって寄贈されました)
実際に \b が何を行うかをしっかり理解してください: これは単語文字 (\w) と、単語文字でない何かとの境界です。 単語文字でないというのは \W かもしれませんが、文字列の最初と最後にも あります。
これは空白と非空白との境界ではなく(違うんです!)、私たちが文を作るのに 使う単語の間のものではありません。
正規表現的に言うと、単語境界は (\b) は「ゼロ幅アサーション」です; これは文字列中の文字ではなく、ある位置での条件を表現しています。
正規表現 /\bPerl\b/ では、"P" の前と "l" の後に単語境界がなければなりません。 "P" の前と "l" の後に単語文字以外の何かがある限り、このパターンは マッチングします。 以下の文字列は /\bPerl\b/ にマッチングします。
"Perl" # no word char before P or after l "Perl " # same as previous (space is not a word char) "'Perl'" # the ' char is not a word char "Perl's" # no word char before P, non-word char after "l"
これらの文字列は /\bPerl\b/ にマッチングしません。
"Perl_" # _ is a word char! "Perler" # no word char before P, but one after l
しかし、単語にマッチングさせるために \b を使う必要はありません。 単語文字に囲まれた非単語文字を探すのに使えます。 これらの文字列は、パターン /\b'\b/ にマッチングします。
"don't" # the ' char is surrounded by "n" and "t" "qep'a'" # the ' char is surrounded by "p" and "a"
これらの文字列は /\b'\b/ にマッチングしません。
"foo'" # there is no word char after non-word '
単語境界ではないということを指定するために、\b の逆である \B も使えます。
パターン /\Bam\B/ では、"a" の前と "m" の後ろに単語文字が必要です。 これらはパターン /\Bam\B/ にマッチングします:
"llama" # "am" surrounded by word chars "Samuel" # same
これらの文字列は /\Bam\B/ にマッチングしません。
"Sam" # no word boundary before "a", but one after "m" "I am Sam" # "am" surrounded by non-word chars
(Anno Siegel によって寄贈されました)
プログラムのどこかでそういった変数が使われているのを見つけてしまうと、 Perl はすべてのパターンマッチングに対してそれに対処することを やらなければなりません。 これは、全てのパターンマッチングに置いて、一部は $` に、一部は $& に、 一部は $' に、という形で文字列全体がコピーされるということを意味します。 従って、ペナルティは長い文字列とパターンを何度もマッチングさせるときに 最も厳しいものとなります。 可能であれば $& や $'、$` を使わないようにすべきなのですが、 それができないのであれば、一度これらの変数を使ってしまったら好きなように 使いましょう。 なぜなら、罰金はすでに払ってしまったのですから。 アルゴリズムの中にはこういった変数を使うことが適切であるものが あるということに注意してください。 リリース 5.005 からは、$& はもはや“高価な”ものではありません。
Perl 5.6.1 から、特殊変数 @- と @+ で $`, $&, $' の機能を 置き換えられるようになりました。 これらの配列はそれぞれのマッチングの開始位置と終了位置へのポインタを 含みます(詳しい話については perlvar を参照してください)ので、 本質的にはこれらから同じ情報が得られますが、 過度の文字列コピーのリスクはありません。
Perl 5.10 では、グローバルなペナルティなしに同じ作業をするための
3 つの特殊変数 ${^MATCH}
, ${^PREMATCH}
, ${^POSTMATCH}
が
追加されました。
Perl 5.10 では、正規表現を /p
修飾子付きでコンパイルしたり
実行したりした場合にのみこれらの変数がセットされます。
\G
を使うと何が良いのですか?次のマッチングを同じ文字列のうち、前回のマッチングを中止したところから
始めるために \G
アンカーを使います。
正規表現エンジンはこのアンカーがあるときには次のマッチングを探すために
何の文字も読み飛ばさないので、\G
は文字列先頭アンカーである ^
と
似ています。
\G
アンカーは典型的には g
フラグと共に使います。
これは次のマッチングの開始位置として pos()
の値を使います。
マッチング演算子がマッチングに成功すると、pos()
の値を最後のマッチングの
次の文字(または次のマッチングの最初の文字; これはあなたがこれをどのように
見るかの問題です)の位置に更新します。
各文字列はそれぞれ独自の pos()
の値を持ちます。
"1122a44" のような文字列で、連続した数字のペアの全てにマッチングし、数字で
ないものが現れたら停止したいとします。
11
と 22
にはマッチングしたいですが、22
と 44
の間に a
が
現れると、a
で停止したいとします。
数字のペアのマッチングは単に a
を読み飛ばし、引き続き 44
に
マッチングします。
$_ = "1122a44"; my @pairs = m/(\d\d)/g; # qw( 11 22 44 )
\G
アンカーを使うと、22
の後のマッチングを a
から始めることを
強制します。
ここでは数字が見付からないのでマッチングできず、従って次のマッチングは
失敗してマッチング演算子は既に見付かったペアを返します。
$_ = "1122a44"; my @pairs = m/\G(\d\d)/g; # qw( 11 22 )
スカラコンテキストにも \G
アンカーが使えます。
g
フラグはまだ必要です。
$_ = "1122a44"; while( m/\G(\d\d)/g ) { print "Found $1\n"; }
文字 a
のマッチングに失敗した後、perl は pos()
をリセットし、
同じ文字列に対する次のマッチングは先頭から行います。
$_ = "1122a44"; while( m/\G(\d\d)/g ) { print "Found $1\n"; }
print "Found $1 after while" if m/(\d\d)/g; # finds "11"
perlop と perlreref に書いているように、c
フラグをつけることで
pos()
のリセットを抑制できます。
引き続くマッチングは、その間に同じ文字列でのマッチングが失敗していても、
直前の成功したマッチングが終了した場所 (pos()
の値) から開始されます。
この場合、while()
ループの後のマッチングは (最後のマッチングが停止した)
a
から開始され、何のアンカーも使っていないので、44
を見つけるために
a
をスキップできます。
$_ = "1122a44"; while( m/\G(\d\d)/gc ) { print "Found $1\n"; }
print "Found $1 after while" if m/(\d\d)/g; # finds "44"
典型的には、トークナイザのようなものの中で、一つのマッチングに失敗した
ときに他のマッチングを試したいときに、\G
アンカーに c
フラグを
つけて使います。
Jeffrey Friedl が、5.004 以降で動作する、これに関する例を提供してくれました。
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; }; } }
それぞれの行において、PARSER
ループはまず数字の列に引き続く単語境界に
マッチングしようと試みます。
このマッチングは直前のマッチングが行われた場所(最初のマッチングの場合は
文字列の先頭)から始められなければなりません。
m/ \G( \d+\b )/gcx
は c
フラグを使っているので、もし文字列がこの
正規表現にマッチングしなければ、perl は pos()
をリセットせず、次の
マッチングは、他のパターンを試す場合でも同じ位置から開始します。
Perl の正規表現は egrep(1)
の DFA (deterministic finite automata,
決定性有限オートマトン)と似たものではあるのですが、
実際のところはバックトラックや後方参照 (backreferencing)のために
NFA として実装されています。
そして、Perl の正規表現は POSIX 形式のものでもありません。
なぜなら、それはすべてのケースにおいて最悪の振る舞いを行うからです
(一部の人は、それが遅さをもたらすにもかからわず、一貫性をもたらすという点を
好んでいるようです)。
上記のことなどに関しての詳細は Jeffrery Friedl による O'Reilly から
出版されている "Mastering Regular Expressions" という本を参照してください。
問題は、grep はそのコンテキストには関係なくリストを返すことです。 これはつまり、Perl にあなたが無視してしまうための戻り値のリストを 作らせるということです。 もしリストが大きいなら、時間とメモリの両方を無駄にします。 あなたの目的がリスト全体に対して反復することなら、for ループを 使ってください。
5.8.1 より前の perl では、map も同じ問題の影響を受けていました。 しかし 5.8.1 から、これは修正され、map はコンテキストを 認識するようになりました - 無効コンテキストではリストは作られません。
Perl 5.6 から、Perl にはあるレベルのマルチバイト文字サポートがあります。 Perl 5.8 以降を推奨します。 対応するマルチバイト文字のレパートリーには、Unicode と、Encode モジュール 経由のレガシーエンコーディングを含みます。 perluniintro, perlunicode, Encode を参照してください。
もしもっと古い Perl に捕まっているなら、Unicode::String
モジュールで
Unicode ができ、Unicode::Map8
と Unicode::Map
のモジュールを使って
文字変換ができます。
日本語エンコーディングを使っているなら、jperl 5.005_03 を使ってみたいかも
しれません。
最後に、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" があるわけではないのです。
これは大きな問題です。
この問題に対処する方法が、どれもうんざりするようなものですが、幾つかあります:
# Make sure adjacent "martian" bytes are no longer adjacent. $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; # above is conceptually similar to: @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 probably unneeded print "found GX!\n", last if $1 eq 'GX'; }
以下は Benjamin Goldberg による、同じことをもう少しましに行うもので、 ゼロ幅否定前方参照アサーションを使っています。
print "found GX!\n" if $martian =~ m/ (?<![A-Z]) (?:[A-Z][A-Z])*? GX /x;
これはもし "martian" 文字 GX が文字列にあれば成功し、そうでなければ 失敗します。 ゼロ幅否定前方参照アサーション (?<!) を使いたくないなら、(?<![A-Z]) を (?:^|[^A-Z]) で置き換えられます。
これには $-[0] と $+[0] に間違ったものが入るという欠点がありますが、普通 これは回避できます。
(brian d foy によって寄贈されました)
マッチング演算子(あるいはその他の正規表現として働くもの)にパターンを ハードコーディングする必要はありません。 後で使うためにパターンを変数に入れることができます。
マッチング演算子はダブルクォートコンテキストなので、ダブルクォート文字列と
同様に変数展開できます。
個の場合、正規表現をユーザー入力として読み込んで、$regex
に保管します。
パターンが $regex
に入れば、この変数をマッチング演算子の中で使えます。
chomp( my $regex = <STDIN> );
if( $string =~ m/$regex/ ) { ... }
$regex
内の全ての正規表現特殊文字は特殊なままで、パターンが
有効でなければ Perl はエラーを出します。
例えば、このパターンには組になっていないかっこがあります。
my $regex = "Unmatched ( paren";
"Two parens to bind them all" =~ m/$regex/;
Perl が正規表現をコンパイルする時に、かっこをマッチング記憶の開始として 扱います。 閉じかっこが見つからないと、エラーが出ます:
Unmatched ( in regex; marked by <-- HERE in m/Unmatched ( <-- HERE paren/ at script line 3.
状況に依存して、いくつかの方法でこれを回避できます。
まず、文字列中のどの文字も特別ではないなら、文字列を使う前に
quotemeta
でエスケープできます。
chomp( my $regex = <STDIN> ); $regex = quotemeta( $regex );
if( $string =~ m/$regex/ ) { ... }
\Q
と \E
のシーケンスを使うことで、これをマッチング演算子の中で直接
行うこともできます。
\Q
は Perl に特殊文字のエスケープを開始する位置を示し、\E
はそれを
終了する位置を示します (さらなる詳細については perlop を
参照してください)。
chomp( my $regex = <STDIN> );
if( $string =~ m/\Q$regex\E/ ) { ... }
あるいは、正規表現クォート演算子である qr//
が使えます(さらなる
詳細については perlop を参照してください)。
これはパターンをクォートし、おそらくはコンパイルされます;
また、パターンに正規表現フラグを適用できます。
chomp( my $input = <STDIN> );
my $regex = qr/$input/is;
$string =~ m/$regex/ # same as m/$input/is;
全体を eval
ブロックで包むことで全てのエラーをトラップしたいかも
しれません。
chomp( my $input = <STDIN> );
eval { if( $string =~ m/\Q$input\E/ ) { ... } }; warn $@ if $@;
または...
my $regex = eval { qr/$input/is }; if( defined $regex ) { $string =~ m/$regex/; } else { warn $@; }
Revision: $Revision$
Date: $Date$
See perlfaq for source control details and availability.
Copyright (c) 1997-2009 Tom Christiansen, Nathan Torkington, and other authors as noted. All rights reserved.
This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.
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.