1. ホーム
  2. スクリプト・コラム
  3. パール

Perl拡張の正規表現コード解析

2022-01-30 17:48:34
コピーコード コードは以下の通りです。

my $ip = "192.168.0.1|192.168.0.2|192.168.0.1";
if ( $ip =~ /
    ^
    (? :
        ((? :\d{1,3}\. {3}\d{1,3})
        (? =
            (? :
                \|(?! \1)(?1)
            )*
            \z
        )
        \|
    )*
    (?1)
    $
    /x ) {
    print "match\n";
}

perlreのドキュメントにしたがって、少しずつ説明します。まず/xですが、これは正規表現からスペースを取り除くためのもので、そうしないと1行で読むのが大変です。次に^ですが、これは一番最初から始まることを意味します。次に(? : で、これはこのブラケットが逆参照$&にクレジットされていないことを意味します。次に ((? :\d{1,3}.) {3}d{1,3}) で、これも(?)がつきます。: があり、これはこの行がipにマッチし、$1としてカウントされることを意味します。次に ((? = があり、これは上の行の後に、この括弧内の定義にマッチする正規表現が必要で、これも$&としてカウントされないことを意味します(この用語は "zero-width positive forward assertion" というのですね)。次にipを区切る|、次に(?!)、これはこの括弧の中にあるものは決して現れてはならないことを意味し、これも$&にカウントされません(用語では"ゼロ幅の負の前方アサーション"、と言いますね)。そして、前の行で捕捉した$1と、前の行で説明したアサーション、つまり|と前のマッチするipの後に重複するipはありえないという意味の▼があり、前の行で$1を捕捉した正規表現、つまりipを重複させずに新しいipを捕捉し続けているという意味の▼があり、その▼に閉じる(?)、その▼がある。: で、これは|ip|を複数回繰り返すことができることを意味します;そして、文字列の境界であり、1行の$の役割に相当し、この場合は交換可能で、ここでは(?!)のチェックを最後まで行うために使われる˶′˶です;そして、(?)を閉じる(?)があり、これは(?)を閉じる(?)を意味します。=; 次に | と ) があり、ここでは ^( に閉じて、ip を繰り返さないという条件を満たす ip| 形式が常に正規にマッチすることを示します; 次に (?1)$ で、最後の ip を定義し、$1 と同じルール、つまり、文字列に少なくとも1つの ip がなければならないことを示します。OK、説明終了。実際には、後ろから前に、むしろ明確な〜〜〜別の:{CODE}の(????)段落のPerlre {Perl 5.12.x以前では、正規表現エンジンがリエントラントではなかったため、遅延したコードは安全に正規表現エンジンを呼び出すことができませんでした(直接的には "m//" や "s//" )、間接的には "split"." や (?R), (????) などの関数で。{CODE})は似たような単純な作業を行うので、linuxのディストリビューションに付属しているperlのバージョンが低いと、ここで(?1)のような単純な書き方はできず、自分で書き直さなければならない。という風に判断できます。
コピーコード コードは以下の通りです。

my $re = $^V lt v5.14 ? '(? :\d{1,3}\.?) {4}' : '(?1)';
my $ip = "192.168.0.1|192.168.0.2|192.168.0.3|192.168.0.4|192.168.0.5";
if ( $ip =~ m/
    ^
    (? :
        ((? :\d{1,3}\...?) {4})
        (? =
            (? :
                \|(?! \1)$re
            )*
            \z
        )
        \|
    )*
    $re
    $
    /x ) {
    print "$1 match\n";
}