如何将这个perl one-liner转换成脚本(特别是多行,全局正则表达式替换)

时间:2012-12-18 09:14:51

标签: regex perl replace

我有一个包含多个XML标记的文件,如:

<Good>Yay!</Good>
<Great>Yup!</Great>
<Bad>booo</Bad>
<Bad>
<Ok>not that great</ok>
</Bad>
<Good>Wheee!</Good>

我想摆脱“坏”标签以及介于两者之间的任何东西。 所以它会变成只是:

<Good>Yay!</Good>
<Great>Yup!</Great>
<Good>Wheee!</Good>

我知道这个单行:

perl -pe "undef $/;s/<Bad>.*?<\/Bad>//msg" < originalFile > newlyStrippedFile

似乎做我想做的一切(除了添加额外的新行,但希望我能够轻松地处理它)

但是我需要把它放在一个脚本中(两个文件被读入命令行,一个带有所有标签,另一个带有要拔出的标签列表),所以同样的事情将被称为几个次。

我遇到了麻烦。要么它只读一行,要么我得错误,或两者兼而有之。

以下是我最近尝试的相关部分:

open ORIGINAL_FILE, $sdb_pathname
  or die "Can't open '$sdb_pathname' : $!";

@sdb_input_array = <ORIGINAL_FILE>;  

close ORIGINAL_FILE;
@sdb_input_scalar=join("",@sdb_input_array);

foreach $tag (@tags) {
  &remove_tag($tag);
}

sub remove_tag 
{
   my($current_tag) = @_;

   $sdb_input_scalar  =~ s/<$current_tag>.*?<\/$current_tag>//msg; 

   open NEWLY_STRIPPED_FILE, $clean_sdb_pathname
     or die "Can't open '$clean_sdb_pathname' : $!";

   print(NEWLY_STRIPPED_FILE $sdb_input_scalar);
   close(NEWLY_STRIPPED_FILE);  

}

这让我“在我的$ sdb_input_scalar =〜行中使用未初始化的值$ sdb_input_scalar替换(s ///)。 和 Filehandle NEWLY_STRIPPED_FILE仅为输入

打开

当然,我的两个文件看起来仍然相同,好像我没有对他们做任何事情。

如果我遗漏了一些明显的东西,我很抱歉,但我对perl来说真的很陌生。有人在工作时给出了8小时的估计来完成这个脚本,我已经用了超过5个小时来安装perl,学习语法并让其他方面正确。我知道有一个XML :: Parser模块,但我发现这些例子在我完成的短时间内非常难以理解。

我必须假设我的正则表达式是正确的,因为单线程工作得非常好。 任何人都可以帮助我根据我的需要进行调整吗?

5 个答案:

答案 0 :(得分:6)

你真的应该使用XML解析器。这几乎可以保证XML文件不会像你期望的那样解析正则表达式。但是,让我们先开始。

你在哪里:

@sdb_input_scalar=join("",@sdb_input_array);

你真的想要:

$sdb_input_scalar=join("",@sdb_input_array);

现在提供其他一些提示。

在脚本的顶部,请确保使用-w标志启用警告,如下所示:

#!/path/to/perl -w

use strict;

一旦你加入use strict它会导致你的几个错误,但这是一件好事。我们将强制执行一些范围和其他良好实践。您现在需要使用my初始化变量(以$,@或%开头)。例如:

my @sdb_input_array = <ORIGINAL_FILE>;

或:

foreach my $tag (@tags) { ... }

不要像你一样打开电话,而是使用三个争论版本:

open ($originalFile, "<", $sdb_pathname)
  or die "Can't open '$sdb_pathname' : $!";

my @sdb_input_array = <$originalFile>;

这将把它设置为只读。见http://perldoc.perl.org/functions/open.html

通常你应该避免对全局变量的依赖。更改您调用remove_tag()的方式:

foreach $tag (@tags) {
  $sdb_input_scalar = remove_tag($sdb_input_scalar, $tag);
}

为了支持这一点,您还需要更改功能:

sub remove_tag 
{
   my($input, $current_tag) = @_;

   $input  =~ s/<$current_tag>.*?<\/$current_tag>//msg; 

   return $input;    
}

您可以在遍历remove_tag函数之后迭代所有标记后写出一次:

   open ($strippedFile, ">", $clean_sdb_pathname)
     or die "Can't open '$clean_sdb_pathname' : $!";

   print $strippedFile $sdb_input_scalar;
   close($strippedFile);

答案 1 :(得分:2)

以下是使用XML::Twig的解决方案:

use warnings;
use strict;

use XML::Twig;

my $xml = XML::Twig->new(
    pretty_print  => 'indented',
    twig_handlers => {
            #Define a sub that will be called for all 'Bad' tags
            Bad => sub {
                $_->set_tag('Good'); 
        }
    }
);

$xml->parse(\*DATA);
$xml->print;

__DATA__
<xml><Good>Yay!</Good><Great>Yup!</Great><Bad>booo</Bad><Bad>
<Ok>not that great</Ok></Bad><Good>Wheee!</Good></xml>

XML::Twig还有parsefile()parsefile_inplace()方法,可直接获取文件名并进行处理 - 正是您所需要的。

这种方法有一点学习曲线,但好处很大。

答案 2 :(得分:2)

首先:不要使用正则表达式来处理XML! 然后,假设问题标题存在疑问,而不是具体的用例。你的单行代码写得更好:

perl -0777 -pe "s/<(Bad)>.*?<\/\1>//msg" < originalFile > newlyStrippedFile

现在,使用Perl本身来“膨胀”单行:

perl -MO=Deparse -0777 -pe "s/<(Bad)>.*?<\/\1>//msg" > oneliner.pl

这就是你得到的:

BEGIN { $/ = undef; $\ = undef; }
LINE: while (defined($_ = <ARGV>)) {
    s[<(Bad)>.*?</\1>][]gms;
}
continue {
    die "-p destination: $!\n" unless print $_;
}

只需添加use strict; use warnings;

答案 3 :(得分:0)

这是使用XML::Twig的解决方案。我假设您的XML文档格式正确,并已将您在其中显示的数据包装在<root>元素中以实现此目的。

$twig对象为<Bad>元素定义了一个 twig处理程序,如果元素在解析过程中出现,则只删除该元素。

解析输入后,$twig-print会显示剩余的XML。

use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new(
  twig_handlers => { Bad => sub { $_->delete } },
  pretty_print => 'record',
);

$twig->parse(<<'END_XML');

<root>
  <Good>Yay!</Good>
  <Great>Yup!</Great>
  <Bad>booo</Bad>
  <Bad>
    <Ok>not that great</Ok>
  </Bad>
  <Good>Wheee!</Good>
</root>

END_XML

$twig->print;

<强>输出

<root>
  <Good>Yay!</Good>
  <Great>Yup!</Great>
  <Good>Wheee!</Good>
</root>

答案 4 :(得分:-1)

这应该可以解决问题:

    $tags=join("",@sdb_input_array);
    print "contents before : $tags \n";
    $tags =~ s/<Bad>.*?<\/Bad>//msg;
    print "content cleaned : $tags \n";

标签变量现在不应带有“BAD”标签 - 唯一的问题是标签线将留下空白的未填充线,以便在GOOD标记线之间有空行 - 但是你可以删除空行作为你的最后一步