web-dev-qa-db-ja.com

Perlの変数として正規表現置換を渡しますか?

正規表現の置換を変数として渡す必要があります。

sub proc {
    my $pattern = shift;
    my $txt = "foo baz";

    $txt =~ $pattern;
}

my $pattern = 's/foo/bar/';
proc($pattern);

もちろん、これは機能しません。置換を評価してみました:

eval("$txt =~ $pattern;");

しかし、それもうまくいきませんでした。私がここで見逃している恐ろしく明白なことは何ですか?

23
ceo

正規表現の置換を変数として渡す必要があります

あなたは?コード参照を渡してみませんか?例:

sub modify
{
  my($text, $code) = @_;
  $code->($text);
  return $text;
}

my $new_text = modify('foo baz', sub { $_[0] =~ s/foo/bar/ });

一般に、「何かを行う何か」をサブルーチンに渡したい場合(質問の場合は「正規表現の置換」)、答えはコードの一部への参照を渡すことです。 高次Perl はこのトピックに関する良い本です。

32
John Siracusa

さて、qr //演算子を使用してREをプリコンパイルできます。ただし、演​​算子(s ///)を渡すことはできません。

$pattern = qr/foo/;

print "match!\n" if $text =~ $pattern;

ただし、置換演算子を渡す必要がある場合は、コードまたは文字列のいずれかを渡す必要があります。

proc('$text =~ s/foo/bar');

sub proc { 
   my $code = shift;

   ...

   eval $code;
}

または、コード:

proc(sub {my $text = shift;  $text =~ s/foo/bar});

sub proc {
   my $code = shift;

   ...

   $code->("some text");
}
8
zigdon
sub proc {
    my($match, $subst) = @_;
    my $txt = "foo baz";
    $txt =~ s/$match/$subst/;
    print "$txt\n";
}

my $matcher = qr/foo/;
my $sub_str = "bar";

proc($matcher, $sub_str);

これはむしろ直接あなたの質問に答えます。さらに多くのことができますが、単純なリテラルとして$ sub_strの代わりにqr //用語を使用すると、拡張正規表現が置き換えられました。

私は最近、いくつかの独特な(ダイアレクトの)SQL型を持つステートメントのパーサー(テストパーサー)を作成する必要がありました。このような行を認識し、3つの型名に分割します。

input: datetime year to second,decimal(16,6), integer

これをデモするために使用したスクリプトは、引用符で囲まれた正規表現を使用していました。

#!/bin/Perl -w
use strict;
while (<>)
{
    chomp;
    print "Read: <$_>\n";
    my($r1) = qr%^input\s*:\s*%i;
    if ($_ =~ $r1)
    {
        print "Found input:\n";
        s%$r1%%;
        print "Residue: <$_>\n";
        my($r3) = qr%(?:year|month|day|hour|minute|second|fraction(?:\([1-5]\))?)%;
        my($r2) = qr%
                        (?:\s*,?\s*)?   # Commas and spaces
                        (
                            (?:money|numeric|decimal)(?:\(\d+(?:,\d+)?\))?   |
                            int(?:eger)?  |
                            smallint      |
                            datetime\s+$r3\s+to\s+$r3
                        )
                    %ix;
        while ($_ =~ m/$r2/)
        {
            print "Got type: <$1>\n";
            s/$r2//;
        }
        print "Residue 2: <$_>\n";
    }
    else
    {
        print "No match:\n";
    }
    print "Next?\n";
}

$ r1などの名前の使用について議論することができます。しかし、それは仕事をしました...それは本番コードではありませんでした。

8

s///は正規表現ではありません。したがって、正規表現として渡すことはできません。

evalは好きではありません。非常に壊れやすく、ボーダーケースがたくさんあります。

Javascriptが採用しているのと同様のアプローチを取るのが最善だと思います。両方の正規表現を渡します(Perlではqr//)および置換のコードリファレンス。たとえば、パラメータを渡してと同じ効果を得るには

s/(\w+)/\u\L$1/g;

あなたは呼び出すことができます

replace($string, qr/(\w+)/, sub { "\u\L$1" }, 'g');

'g'修飾子は実際には正規表現のフラグではないことに注意してください(正規表現にアタッチすることはJavascriptの設計ミスだと思います)。そのため、3番目のパラメーターで渡すことにしました。

APIが決定したら、次に実装を行うことができます。

sub replace {
    my($string, $find, $replace, $global) = @_;
    unless($global) {
        $string =~ s($find){ $replace->() }e;
    } else {
        $string =~ s($find){ $replace->() }ge;
    }
    return $string;
}

試してみよう:

print replace('content-TYPE', qr/(\w+)/, sub { "\u\L$1" }, 'g');

結果:

コンテンツタイプ

それは私には良さそうです。

5
bart
eval "$ txt =〜$ pattern";
eval "\" foo baz\"= 〜s/foo/bar /"

これはうまくいくでしょう:

eval "\ $ txt =〜$ pattern"

zigdonのソリューションは何でも実行でき、置換文字列が静的である場合、Jonathanのソリューションは非常に適しています。最初のものよりも構造化され、2番目のものよりも柔軟なものが必要な場合は、ハイブリッドをお勧めします。

sub proc {
 my $ pattern = shift; 
 my $ code = shift; 
 my $ txt = "foo baz"; 
 $ txt = 〜s/$ pattern/$ code->()/ e; 
 print "$ txt\n"; 
} 
 my $ pattern = qr/foo /;
 proc($ pattern、sub {"bar"}); #==> bar baz 
 proc($ pattern、sub {"\ U $&"}); #==> FOO baz
5
ephemient

おそらくあなたはあなたのアプローチを再考するかもしれません。

関数に正規表現置換を渡したい場合は、おそらく関数が他のソース(ファイル、ソケットなどからの読み取り)から操作対象のテキストを取得するためです。しかし、正規表現を正規表現の置換と混同しています。

s/foo/bar/には、実際には正規表現( "/ foo /")と、式と一致するものを置き換える置換( "bar")があります。これまでに試したアプローチでは、主に式内の特殊文字がevalに干渉するか、補間される可能性があるため、evalを使用しようとすると問題が発生しました(つまり、評価の過程で)をむさぼり食った。

したがって、代わりに、ルーチンに2つの引数(式と置換)を渡してみてください。

sub apply_regex {
    my $regex = shift;
    my $subst = shift || ''; # No subst string will mean matches are "deleted"

    # some setup and processing happens...

    # time to make use of the regex that was passed in:
    while (defined($_ = <$some_filehandle>)) {
        s/$regex/$subst/g; # You can decide if you want to use /g etc.
    }

    # rest of processing...
}

このアプローチには、追加の利点があります。正規表現パターンないに特殊文字が含まれている場合は、直接渡すことができます。

apply_regex('foo', 'bar');

または、そうであれば、qr// quoting-operatorを使用して正規表現オブジェクトを作成し、それを最初のパラメーターとして渡すことができます。

apply_regex(qr{(foo|bar)}, 'baz');
apply_regex(qr/[ab]+/, '(one or more of "a" or "b")');
apply_regex(qr|\d+|); # Delete any sequences of digits

何よりも、このタスクにevalやコード参照/クロージャを使用する必要はありません。それは、デバッグを必要以上に難しくする可能性のある複雑さを追加するだけです。

ランディ

4
rjray

私はそれを行うためのおそらくより良い方法を見つけました:

sub proc {
    my ($pattern, $replacement) = @_;
    my $txt = "foo baz";

    $txt =~ s/$pattern/$replacement/g;  # This substitution is global.
}

my $pattern = qr/foo/;  # qr means the regex is pre-compiled.
my $replacement = 'bar';

proc($pattern, $replacement);

置換のフラグを可変にする必要がある場合は、次を使用できます。

sub proc {
    my ($pattern, $replacement, $flags) = @_;
    my $txt = "foo baz";

    eval('$txt =~ s/$pattern/$replacement/' . $flags);
}

proc(qr/foo/, 'bar', 'g');

エスケープする必要はないことに注意してください/置換文字列。

0
Aloso

私はこのトリックを採用した大量のファイル名を変更するための非常に単純なスクリプトを持っています:

#!/opt/local/bin/Perl
sub oops { die "Usage : sednames s/old/new [files ..]\n"; }
oops if ($#ARGV < 0);

$regex = eval 'sub { $_ = $_[0]; ' . shift(@ARGV) . '; return $_; }';
sub regex_rename { foreach (<$_[0]>) {
    rename("$_", &$regex($_));
} }

if ($#ARGV < 0) {  regex_rename("*");  }
else {  regex_rename(@ARGV);  }

$_のようにs/old/newを変更するPerlコマンドは、ファイルを変更するために使用できます。

正規表現を一度だけコンパイルする必要があるように、evalを使用することにしました。 eval$_には、単純に使用できなかったいくつかの不思議があります。

eval 'sub { ' . shift(@ARGV) . ' }';

この&$regexは確かに$_を変更しますが; renameを呼び出す前に、"$_"$_を評価する必要があります。はい、他のみんなが言ったように、evalは非常に壊れやすいです。

0
Jeff Burdges