使用<>和正则表达式搜索和替换文本文件中的元素

时间:2013-01-14 16:00:00

标签: regex perl

我正在通过 Learning Perl ,第9章“使用正则表达式处理文本”。

以下是两章章末练习:

  
      
  1. 编写一个程序,为目前为止的所有练习答案添加版权行,在“shebang”行后面的文件中放置## Copyright (c) 20XX by Yours Truly之类的行。假设将使用文件名调用程序,以便在命令行上进行编辑。

  2.   
  3. 修改以前的程序,使其不会编辑已包含版权行的文件。作为提示,您可能需要知道菱形运算符正在读取的文件的名称是$ ARGV。

  4.   

这是我尝试的解决方案:

#!/usr/bin/env perl

use 5.014;
use warnings;

my $shebang     = '(#!/usr/bin/env perl|#!/usr/bin/perl)'; 
my $copyright   = '# Copyright (c) 20XX Yours Truly'; 

$^I = ".bak";

while (<>) {
    unless (/$copyright/mi) {
        s/($shebang)/$1\n$copyright/mig;
    }
    print;
}

使用perl ch9.pl sample_perl_script.pl在命令行上运行。

我的目标是:

  • 保持原始的shebang完好无损,无论路径如何。
  • 只需一次循环<>
  • 检查是否存在版权声明。
  • 如果没有,请添加它(因此尝试使用unless { ... })。

这适用于问题的第一部分(添加版权线),但不适用于第二部分(检查以确保版权尚不存在)。

我的问题是:为什么?为什么在运行程序时unless完全被忽略了?

我偷看了附录,本书的建议解决方案是创建一个哈希来跟踪$ARGV的文件名,并将文件传递两次。首先要删除已经有版权声明的文件,然后执行搜索/替换。像这样:

my %do_these;
foreach (@ARGV) {
    $do_these{$_} = 1;
}

while (<>) { 
    if (/\A## Copyright/) {
        delete $do_these{$ARGV};
    }
}

@ARGV = sort keys %do_these; 
$^I = ".bak";
while (<>) {
    if (/\A#!/) {
        $_ .= "## Copyright (c) 20XX by Yours Truly\n";
    }
    print;
}

这当然有效,但似乎是工作的两倍。我试图通过我的方法在单个while (<>) { ... }循环中找到一种方法来做到这一点,并且更好地理解钻石操作符的工作原理。

如果我的方法完全偏离基础,请解释原因并且不要放过我的感受。我对比我的自我更全面的了解更感兴趣。

2 个答案:

答案 0 :(得分:4)

你的书的方法 是愚蠢的。实际上,我认为perl正在bar,因为你的版权声明有(之类的特殊字符。

你想要的是quotemeta功能。 Link

我会改变你的程序:

while (<>) {
    my $copyright2 = quotemeta $copyright;
    unless (/$copyright2/mi) {
        s/($shebang)/$1\n$copyright/mig;
    }
    print;
}

如果不起作用,请道歉。我写perl已经有一段时间了。

答案 1 :(得分:3)

您的unless不起作用,因为版权与shebang不在同一行。菱形运算符读取一行直到$/的第一个值,默认为换行符。您的程序将在不包含版权的所有上执行替换。

由于这是perl,因此有很多方法可以解决它。最直接的方法可能是取消设置$/并啜饮文件(将其全部读成一行)。这样,如果文件的第二行有版权声明,您可以立即查看。 E.g:

local $/;                                     # slurp the file
while (<>) {
    s/^.*\n\K(?!\Q$copyright\E)/$copyright/;  # negative lookahead assertion
    print;
}

您还可以直接在文件中检查第2行,而不会诋毁文件:

while (<>) {
    if ($. == 2) {
         unless (/\Q$copyright/) {
               print "$copyright\n";
         }
    }
    print;
    close ARGV if eof;                # this will reset the line counter $.
}

请注意,Nick ODell是正确的,您的版权字符串包含必须转义的元字符(即括号)。我在上面使用了\Q ... \E转义序列。

另请注意,您不需要非常具体地检查shebang,这更容易让您在稍微变化的线路上绊倒。