6

特定の正規表現をファイル名に適用する単純な perl スクリプトを作成しようとしていますが、正規表現を引数としてスクリプトに渡すのに問題があります。

私ができるようにしたいのは、次のようなものです:

> myscript 's/hi/bye/i' hi.h
bye.h
>

このコードを作成しました

#!/utils/bin/perl -w
use strict;
use warnings;

my $n_args = $#ARGV + 1;
my $regex =  $ARGV[0];
for(my $i=1; $i<$n_args; $i++) {
  my $file = $ARGV[$i];

  $file =~ $regex;
  print "OUTPUT: $file\n";
}

どうやら正規表現の置き換えには使用できないため、qrを使用できません(ただし、これのソースはフォーラムの投稿であるため、間違っていることが証明されてうれしいです)。

2 つの部分を別々の文字列として渡したり、perl スクリプトで正規表現を手動で実行したりすることは避けたいと思います。

このような引数として正規表現を渡すことは可能ですか?もしそうなら、それを行う最良の方法は何ですか?

4

4 に答える 4

9

それを行う方法は複数あると思います。

イヴアルウェイ:

基本的に正規表現を送信すると、結果を取得するために評価できます。このような:

my @args = ('s/hi/bye/', 'hi.h');
my ($regex, @filenames) = @args;
for my $file (@filenames) {
  eval("\$file =~ $regex");
  print "OUTPUT: $file\n";
}

もちろん、この方法をたどると、非常に厄介な驚きがいくつかあります。たとえば、次の一連の引数を渡すことを検討してください。

...
my @args = ('s/hi/bye/; print qq{MINE IS AN EVIL LAUGH!\n}', 'hi.h');
...

はい、それはあなたを最も笑うでしょう evaイリー。

安全な方法:

my ($regex_expr, @filenames) = @args;
my ($substr, $replace) = $regex_expr =~ m#^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/#;
for my $file (@filenames) {
  $file =~ s/$substr/$replace/;
  print "OUTPUT: $file\n";
}

ご覧のとおり、与えられた式を 2 つの部分に解析し、これらの部分を使用して完全な演算子を構築します。明らかに、このアプローチは柔軟性に欠けますが、もちろん、はるかに安全です。

最も簡単な方法:

my ($search, $replace, @filenames) = @args;
for my $file (@filenames) {
  $file =~ s/$search/$replace/;
  print "OUTPUT: $file\n";
}

はい、そうです-正規表現の解析はまったくありません! ここで何が起こるかというと、単一の引数ではなく、'検索パターン' と '置換文字列' の 2 つの引数を取ることにしたことです。これにより、スクリプトが前のものよりも柔軟性が低下しますか? いいえ、多かれ少なかれ定期的に正規表現を解析する必要があったためです。しかし今では、ユーザーはコマンドに与えられたすべてのデータを明確に理解しており、通常はかなり改善されています. )

両方の例の @args は @ARGV 配列に対応します。

于 2012-09-14T11:31:20.920 に答える
4

s/a/b/i単なる正規表現ではなく演算子であるため、eval適切に解釈する場合は使用する必要があります。

#!/usr/bin/env perl

use warnings;
use strict;

my $regex = shift;
my $sub = eval "sub { \$_[0] =~ $regex; }";

foreach my $file (@ARGV) {
    &$sub($file);
    print "OUTPUT: $file\n";
}

ここでのトリックは、この「コードのビット」を文字列に置き換えて、匿名サブルーチン$_[0] =~ s/a/b/i;(またはそれを渡すコード) を定義する Perl コードを生成し、それを使用evalしてそのコードをコンパイルし、私ができるコード参照を提供することです。ループ内から呼び出します。

$ test.pl 's/foo/bar/' foo nicefood
OUTPUT: bar
OUTPUT: nicebard

$ test.pl 'tr/o/e/' foo nicefood
OUTPUT: fee
OUTPUT: nicefeed

eval "\$file =~ $regex;"これは、前もって一度だけではなく、反復ごとにコンパイルおよび評価されるため、ループ内に配置するよりも効率的です。

警告の言葉eval-raina77owの回答が説明しevalているように、常に信頼できるソースから入力を取得していることを100%確信していない限り、避けるべきです...

于 2012-09-14T11:32:08.843 に答える
2

s/a/b/i正規表現ではありません。正規表現と置換です。文字列evalを使用しない限り、この作業はかなり難しいかもしれません(検討s{a}<b>eするなど)。

于 2012-09-14T11:17:06.437 に答える
2

問題は、本当に渡す必要があるのは引数だけなのに、perl 演算子を渡そうとしていることです。

myscript hi bye hi.h

スクリプト内:

my ($find, $replace, @files) = @ARGV;
...
$file =~ s/$find/$replace/i;

あなたのコードは少し不格好です。必要なのはこれだけです:

use strict;
use warnings;

my ($find, $replace, @files) = @ARGV;
for my $file (@files) {
    $file =~ s/$find/$replace/i;
    print "$file\n";
}

この方法では、正規表現で などのメタ文字を使用できることに注意してください\w{2}foo?。これは良いことでもあり、悪いことでもあります。すべての文字を文字どおりに解釈する (メタ文字を無効にする) には、次\Q ... \Eのように使用できます。

... s/\Q$find\E/$replace/i;
于 2012-09-14T14:20:34.873 に答える