使用perl中的替换运算符跳过字符串中的特定位置

时间:2012-08-11 11:56:39

标签: perl skip string-substitution

昨天,我陷入了一个perl脚本。让我简化一下,假设有一个字符串(比如ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD),首先我要在“E”出现的每个位置打破它,其次,在用户想要的位置打破它。但是,条件是,程序不应该在E跟随P的那些站点切割。例如,在这个序列中有6个Es,所以一个应该得到7个碎片,但是当2个Es跟随P时,一个将得到5个输出中的片段。

我需要有关第二种情况的帮助。假设用户不想在序列中的E的第5和第10个位置切断此序列,那么应该让程序只跳过这两个站点的相应脚本应该是什么?我的第一个案例的脚本是:

my $otext = 'ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD';

$otext=~ s/([E])/$1=/g; #Main cut rule.

$otext=~ s/=P/P/g;

@output = split( /\=/, $otext);

print "@output";

请帮忙!

2 个答案:

答案 0 :(得分:4)

要拆分“E”,除非它后面跟着“P”,你应该使用否定的前瞻断言。

来自perldoc perlre“Look-Around Assertions”部分:

  
      
  • (?图案)
      零宽度负前瞻断言。
      例如/foo(?!bar)/匹配任何未出现“bar”的“foo”。
  •   
my $otext = 'ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD'; 
#                E    E    EP    E    EP    E
my @output=split(/E(?!P)/, $otext); 
use Data::Dumper; print Data::Dumper->Dump([\@output]);"

$VAR1 = [
          'ABCD',
          'ABCD',
          'ABCDEPABCD',
          'ABCDEPABCD',
          'ABCD'
        ];

现在,为了不减少出现#2和#4,你可以做两件事:

  1. 编写一个非常奇特的正则表达式,在给定的出现时自动无法匹配。为了完整起见,我会把这个留给其他人尝试回答。

  2. 简单地将正确的碎片拼接在一起。

    我太聪明了,没有想出一个很好的惯用方法,但是简单而肮脏的方式是:

      my %no_cuts = map { ($_=>1) } (2,4); # Do not cut in positions 2,4
      my @output_final;
      for(my $i=0; $i < @output; $i++) {
          if ($no_cuts{$i}) {
              $output_final[-1] .= $output[$i];
          } else {
              push @output_final, $output[$i];
          } 
      }
      print Data::Dumper->Dump([\@output_final];
    
      $VAR1 = [
                'ABCD',
                'ABCDABCDEPABCD',
                'ABCDEPABCDABCD'
              ];
    

    或者,更简单:

      my %no_cuts = map { ($_=>1) } (2,4); # Do not cut in positions 2,4
      for(my $i=0; $i < @output; $i++) {
          $output[$i-1] .= $output[$i]; 
          $output[$i]=undef; # Make the slot empty
      }
      my @output_final = grep {$_} @output; # Skip empty slots
      print Data::Dumper->Dump([\@output_final];
    
      $VAR1 = [
                'ABCD',
                'ABCDABCDEPABCD',
                'ABCDEPABCDABCD'
              ];
    

答案 1 :(得分:0)

这是一个利用两个事实的肮脏技巧:

  • 普通文本字符串永远不会包含空字节(如果您不知道空字节是什么,您应该作为程序员:http://en.wikipedia.org/wiki/Null_character和nb。它与数字0或者数字0不同字符0)。
  • perl strings 可以包含空字节,如果你把它们放在那里,但要小心,因为这可能搞砸了一些perl内部函数。

“小心”只是需要注意的一点。无论如何,我们的想法是在您不想要中断的位置替换空字节:

my $s = "ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD";

my @nobreak = (4,9);

foreach (@nobreak) {
    substr($s, $_, 1) = "\0";
}

"\0"是一个表示空字节的转义序列,如"\t"是一个标签。再说一遍:它不是角色0.我使用4和9因为那些位置有E。如果您现在打印字符串,它看起来像:

ABCDABCDABCDEPABCDEABCDEPABCDEABCD

因为空字节不显示,但是它们存在,我们将在稍后将它们交换出来。首先是分裂:

my @a = split(/E(?!P)/, $s);

然后将零字节交换回来:

$_ =~ s/\0/E/g foreach (@a);

如果您现在打印@a,则会获得:

ABCDEABCDEABCDEPABCD
ABCDEPABCD
ABCD

这正是你想要的。请注意,split会删除分隔符(在本例中为E);如果你打算保留那些你可以在之后重新加上它们。如果分隔符来自更动态的正则表达式,则稍微复杂一点,请参见此处:

http://perlmeme.org/howtos/perlfunc/split_function.html

“示例9.保留分隔符”

如果@nobreak位置有可能不是E,那么你必须在换掉它们时跟踪它们,以确保再次替换正确的角色。