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,因为它显然不能用于替换正则表达式(尽管我的来源是论坛帖子,所以我很高兴被证明是错误的)。

我宁愿避免将这两个部分作为单独的字符串传递并在 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');
...

是的,它最会嘲笑你一种意利。

安全的方式:

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

如您所见,我们将提供给我们的表达式解析为两部分,然后使用这些部分构建一个完整的运算符。显然,这种方法不太灵活,但当然,它更安全。

最简单的方法:

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

是的,没错——根本没有正则表达式解析!这里发生的情况是我们决定采用两个参数——“搜索模式”和“替换字符串”——而不是单个参数。它会使我们的脚本不如前一个灵活吗?不,因为我们仍然或多或少地定期解析正则表达式。但是现在用户清楚地了解了赋予命令的所有数据,这通常是一个很大的改进。)

两个示例中的 @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 回答