【问题标题】:grep vs Perl. Should I mod this to work, or should I just replace it with Perl code?grep 与 Perl。我应该修改它以使其工作,还是应该用 Perl 代码替换它?
【发布时间】:2010-11-08 04:20:18
【问题描述】:

我正在写一些东西,允许用户搜索他们的日志。目前我有这个,其中 $in{'SEARCH'} 是他们正在搜索的字符串。

open(COMMAND, "grep \"$in{'SEARCH'}\" /home/$palace/palace/logs/$logfile | tail -n $NumLines |");
    $f = <COMMAND>;
    if  ($f) {
        print $Title;
        print "<div id=log>\n";
            do {  print $f."<br>";} while ($f = <COMMAND>);
        print "</div>\n";
    } else {print $Error; }
close(COMMAND);

但是我注意到他们很容易通过使用双引号 (") 或反斜杠来欺骗脚本和 grep 命令出错。因此我添加了这段代码:

$in{'SEARCH'} =~ s|\\|\\\\|g;
$in{'SEARCH'} =~ s|"|\Q\"\E|g;

open(COMMAND, "grep \"$in{'SEARCH'}\" /home/$palace/palace/logs/$logfile | tail -n $NumLines |");
    $f = <COMMAND>;
    if  ($f) {
        print $Title;
        print "<div id=log>\n";
            do {  print $f."<br>";} while ($f = <COMMAND>);
        print "</div>\n";
    } else {print $Error; }
close(COMMAND);

但是,我仍然遇到问题。 grep 命令不喜欢其搜索中的 \ 给出类似的错误

grep "\\" /home/test/palace/logs/chat.log
grep:尾部反斜杠

我是否应该继续尝试使用 grep 命令,如果是这样,有什么好的 Perl 函数可以帮助去除有助于 grep 命令的奇怪字符,比如让“成为 \”等。或者,我应该使用 Perl 代码来完成同样的事情,而不是乱七八糟,即使我已经阅读它不会像 grep 一样快?


更新:美国东部标准时间 2009 年 7 月 5 日下午 5:20


许多人贡献了代码,尤其是那些试图比系统 grep 更快的人。到目前为止,它仍然是最快的。以下是基准测试的结果:

使用系统 grep:

Top   of  file: 1 wallclock secs ( 0.00 usr 0.01 sys + 0.13 cusr 0.15 csys = 0.29 CPU)
Bottom of file: 1 wallclock secs ( 0.00 usr 0.00 sys + 0.21 cusr 0.18 csys = 0.39 CPU)

使用 Hypneks 示例(推送和移位):

Top   of  file: 4 wallclock secs ( 3.78 usr + 0.19 sys = 3.97 CPU)
Bottom of file: 4 wallclock secs ( 3.86 usr + 0.19 sys = 4.05 CPU)

使用我的 perl 示例(使用反向命令):

Top   of  file: 6 wallclock secs ( 4.76 usr + 1.45 sys = 6.21 CPU)
Bottom of file: 5 wallclock secs ( 3.93 usr + 1.44 sys = 5.37 CPU)

使用我的 File::ReadBackwards:

Top   of  file:11 wallclock secs (11.20 usr + 0.11 sys = 11.31 CPU)
Bottom of file: 1 wallclock secs ( 0.59 usr + 0.01 sys = 0.60 CPU)

使用 xcramps 示例(内置 grep):

Top   of  file: 9 wallclock secs ( 8.04 usr + 0.47 sys = 8.51 CPU)
Bottom of file: 8 wallclock secs ( 8.16 usr + 0.43 sys = 8.59 CPU)

【问题讨论】:

    标签: regex perl bash grep


    【解决方案1】:

    我同意 Ry4an 的观点,即使用 Perl 可能是明智的。

    如果你想修复你的代码,你需要(应该)在你不希望 shell 搞乱的参数周围使用单引号 - 而不是双引号。例如,反引号或“$(...)”符号将执行双引号内的命令,“${variable}”等将被扩展。处理所有的符号太像辛苦了。使用单引号要简单得多。请注意,嵌入的单引号需要替换为序列“'\''”(这很有趣!);双引号只是围绕您替换单引号的四个字符(假设整个字符串嵌入在单引号中)。第一个引号关闭当前引用的字符串;反斜杠,单引号表示字面单引号;最后一个单引号开始一个新的单引号字符串。您不需要转义任何其他字符。


    获取您的代码:

    my $search = $in{SEARCH};
    $search =~ s/'/'\\''/g;
    
    open(COMMAND, "grep '$search' /home/$palace/palace/logs/$logfile | tail -n $NumLines |");
    $f = <COMMAND>;
    if  ($f)
    {
        print $Title;
        print "<div id=log>\n";
        do { print $f."<br>";} while ($f = <COMMAND>);
        print "</div>\n";
    }
    else
    {
        print $Error;
    }
    close(COMMAND);
    

    【讨论】:

    • 这是我最终使用的解决方案。单引号比转义正确的字符要容易得多。如果您正在寻找严格 PERL 的解决方案,请查看 Hynek -Pichi- Vychodil 的帖子。 Hynek -Pichi- Vychodil 拥有最快的 PERL 代码,但 PERL 的默认正则表达式系统比 grep 的慢。 blackkettle 对网站的评论对此进行了解释:swtch.com/~rsc/regexp/regexp1.html
    【解决方案2】:

    绝对只使用 Perl。将所有工作保持在一个进程中会加快速度,而使用用户指定的参数调用子命令只是在乞求利用。如果您决定坚持使用子命令 quotemeta 可能值得一看。

    【讨论】:

    • 不错的功能,但问题是搜索也应该支持正则表达式,因此使用 quotemeta 它将 * 变成 /* ,然后它根本不匹配它应该如何。
    【解决方案3】:

    试试这个方法:

    my $re = qr/\Q$in{'SEARCH'}\E/;
    my @lines;
    while (<$fh>) {
        next unless m/$re/;
        push @lines, $_;
        shift @lines if @lines > $NumLines;
    }
    
    print @lines;
    

    【讨论】:

    • 这就是我上面所说的。 while () 在标量上下文中运行,这意味着它每次询问 $fh 一行,而不是使用反向,这会将整个文件加载到内存中。
    【解决方案4】:

    您可以通过单独指定参数来完全绕过 shell 引用问题:

    open my $grepfh, "-|", "grep", "-F", $in{'SEARCH'}, "/home/$palace/palace/logs/$logfile" or die "grep open error: $! $?\n";
    

    包括尾部,没有任何错误检查:

    open( my $resultfh, '-|' )
    or do {
        pipe(my $rdfh, my $wrfh);
        if (fork) {    
            open( STDOUT, '>&', $wrfh );
            exec( "grep", @grepargs );
        }
        else {
            open( STDIN, '<&', $rdfh );
            exec( "tail", @tailargs );
        }
    };
    while (my $line = readline($resultfh)) {
        ....
    }
    

    【讨论】:

    • 这对我不起作用。 a) 没有尾命令 b) 您使用 -F,它是快速 grep。我需要正则表达式。 c) 从不指定要 grep 的文件
    • a) 是的,你必须单独做尾巴 b) 我做了 -F 根据你与 /Q.../E 匹配的答案;如果你不想要 -F,你不希望你的 perl 匹配 c) 哎呀,添加
    【解决方案5】:

    我对它的工作方式做了一个快速的 perl 文章,我不确定这是优化的还是最好的方法。但这里是:

    my $num_matches = 0;
    my $logdir = "/home/$palace/palace/logs/$logfile";
    open my $fh, "<", "$logdir" or die "Can't open [$logdir]: $!";  
    
    
    print $Title;
    print "<div id=log>\n";
    
    
    @lines = reverse <$fh>;
    foreach $line (@lines) {
        #print "<b>$line</b> - $num_matches<br>";
        if ($line =~ m/\Q$in{'SEARCH'}\E/){
            print $line."<br>";
            $num_matches ++;
        }
        if ($num_matches >= $NumLines) {
            last;
        }
    }
    
    print "</div>\n";
    

    我确实想指出,我以 PERL 方式(上图)与我的 grep 方式(原始问题)运行基准测试 (http://perldoc.perl.org/Benchmark.html),结果如下:

    PERL 方式: 代码耗时:4 wallclock secs (2.89 usr + 0.96 sys = 3.85 CPU)

    GREP 方式: 代码耗时:1 wallclock secs (0.02 usr 0.04 sys + 0.31 cusr 0.14 csys = 0.51 CPU)

    所以我需要优化我的 Perl 代码。有任何想法吗?我认为通过反向读取文件,它可以尽快找到匹配项并且不需要进一步读取,将其从读取整个文件到内存中保存。但是,我猜反向命令无论如何都会读取内存中的文件。也许我应该使用 system(tac $file) 并阅读它,但我再次试图避免外部命令调用。

    【讨论】:

    • 不要将整个文件读入数组进行反向读取,而是使用 File::ReadBackwards。
    • 我只需要默认包含的库。
    • 更新:我得出的结论是,不扩展我的代码以允许某些 CPAN 模块是愚蠢的。我最初决定不这样做的原因是因为我正在 webmin 中编写一个面板,并且我希望它像普通的 webmin 模块一样安装,据我了解,这意味着最终用户必须手动安装所需的 CPAN 模块.相反,我决定创建一个名为 cpan 的目录,并在那里下载了贷款 .pm 文件,然后对其进行引用。
    • 即使使用 File::ReadBackwards,PERL 也需要 16 秒才能读取文件深处的匹配项,其中 grep |尾部连击需要 0.1 秒。呸呸呸
    • 时间上的差异可能与您使用的正则表达式有关。 grep 正则表达式引擎采用 Thompson 算法进行正则表达式编译和搜索,这在输入中大致是线性的。然而 Perl(以及 Ruby、PHP、Python 等)实现采用了一种方法(出于不同的原因),它需要指数时间来处理某些输入,swtch.com/~rsc/regexp/regexp1.html
    【解决方案6】:

    在性能方面,Perl 非常出色。它可能不如 grep 过程快,但我认为最终用户可能不会注意到差异。

    由于您的目标之一是控制用户输入,我建议您使用 Perl,因为它可以让您尽可能多地控制用户输入,因为您现在或将来可能需要。

    【讨论】:

      【解决方案7】:

      Perl 有一个内置的 grep:

      open(IN, $file) || die;
      @result = grep {/\Q$in{'SEARCH'}\E/} <IN>;
      close(IN);
      
      # this is the equivalent of your tail
      $end = $#result;
      $begin = $end - $NumLines + 1;
      $begin = 0 if $begin < 0;
      print "@result[$begin..$end]";
      

      【讨论】:

      • 这一点也不差,但是linux grep命令在性能方面还是把它吹走了。
      • 和“移位”解决方案,它是如何衡量的?
      • 我刚刚检查了基准,实际上我在这个上有点错误,它是最慢的之一。请参阅我在问题中的更新以了解基准时间。除了系统grep之外,Shift解决方案是最快的
      【解决方案8】:

      愚蠢的想法,但是当行中没有单引号时使用带单引号的 grep 或在有单引号时使用 perl 怎么样?这样您就可以同时获得快速的性能和安全性。

      【讨论】:

      • 我现在使用系统 grep 的解决方案,我有它,所以它逃脱了 ',这是一个很好的解决方案。现在这个线程更多是关于找出为什么 PERL 不如 grep 快。但我认为我们是根据 Thompson 的想法发现的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-14
      • 1970-01-01
      • 1970-01-01
      • 2016-11-18
      • 2010-11-30
      • 1970-01-01
      相关资源
      最近更新 更多