我该如何在Perl中实现Unix的grep命令?

9
我该如何在Perl中实现Unix的grep命令?我尝试使用Perl内置的grep,但以下代码无法正常工作:
$pattern = @ARGV[0];
$file= @ARGV[1];

open($fp,$file);

@arr = <$fp>;

@lines = grep $pattern, @arr;

close($fp);
print @lines;

顺便说一下,我只是尝试基本的 grep 功能而不是完整的功能,其次我不想自己进行字符串解析。我想使用内置的 grep 或者 Perl 的某些函数。

提前感谢 :)

5个回答

16

由于您已经接受了一个答案,我写下这个答案供今后寻找类似问题的读者参考,但并非完全适用于您的问题:

正如其他人已经回答过的那样,使用 Perl 模拟 grep 的方法是使用在线方法。如果要将 Perl 用作“更好”的 grep(以及 find 和 cut 等),我推荐阅读 minimal perl 这本书,而且你很幸运,因为其中一章 'perl as a "better" grep' 是样例章节之一。

这里有更多的示例,灵感来自于这本书:

perl -wnle '/foo/ and print' null.txt  # normal grep
perl -wnle '/foo/ and print "$ARGV: $_"' null.txt # grep -H
perl -wnle '/foo/ and print $ARGV and close ARGV' null_1.txt null_2.txt # grep -l

在上一个例子中,ARGV 是当前文件句柄,和使用 -l 一样,你想要找到匹配的文件并打印文件名,在文件中第一次匹配后继续查找下一个文件。
此外,你也可以按段落搜索而不是按行搜索:
$ perl -00 -wnl -e '/\bBRIBE\b/i and print;' SenQ.testimony
I knew I'd be in trouble if
I ACCEPTED THE BRIBE!
So I did not.

My minimum bribe is $100k, and she only offered me $50k,
so to preserve my pricing power, I refused it.

或者只找到第一个匹配项:

$ perl -00 -wnl -e '/\bBRIBE\b/i and close ARGV;' SenQ.testimony
I knew I would be in trouble if
I ACCEPTED THE BRIBE!
So I did not.

最后,如果你问到grep和perl,我认为我应该提到ACK。它在perl中实现了grep功能并扩展了它。这是一个很棒的工具,作为额外加分,你也可以将其作为CPAN包安装。我一直使用命令行,不知道你是否可以直接从你的perl程序中访问它的方法,但这将非常好。


14
在Perl中,我们使用@来引用整个数组。但是要引用标量类型的单个元素时,我们使用$。因此,在这些代码行中,您需要使用$而不是@
$pattern = @ARGV[0];
$file= @ARGV[1];

同样

这个

@lines = grep $pattern, @arr;

应该是这样的

@lines = grep /$pattern/, @arr;

Perl中的grep函数通常使用以下语法:

grep EXPR,LIST
它对LIST中的每个元素评估EXPR,并返回由那些表达式评估为真的元素组成的列表值。 在您的情况下,EXPR正在搜索数组@arr中的模式$pattern。要进行搜索,您需要使用/PATTERN/而不是/。字符串$pattern将被评估为true或false。

2
@coddadict,你优秀地修复了代码中的错误,但是这个问题和用户使用的方法将其转化为伪“xy问题”,因此这是那些教条主义可能无法涵盖的情况之一。我本来会建议他不要同时读取整个文件(他想模拟以行为导向的grep),我会建议他使用grep{}而不是grep(),以养成良好的习惯,并使用三个参数open。甚至进一步向他展示在线方法(或者while逐行阅读)也是一个加分项。 - Pablo Marin-Garcia

12
当然,codaddict的回答是正确的,但我想添加一些备注:
你应该总是在脚本的开头加上这两行代码:
use strict;
use warnings;

使用三个参数open并测试错误:

open my $fh, '<', $file or die "unable to open '$file' for reading : $!";

由于使用了use strict,您必须声明所有变量。因此,您的脚本将会是这样:

#!/usr/bin/perl

use strict;
use warnings;

my $pattern = $ARGV[0];
my $file = $ARGV[1];

open $fh, '<', $file or die "unable to open file '$file' for reading : $!";
my @arr = <$fh>;
close $fh;  # close as soon as possible

my @lines = grep /$pattern/, @arr;

print @lines;

如果你的文件很大,你可以避免将它全部读入内存:

#!/usr/bin/perl
use strict;
use warnings;

my $pattern = qr/$ARGV[0]/;
my $file= $ARGV[1];
print "pattern=$pattern\n";

my @lines;
open my $fh, '<', $file or die "unable to open file '$file' for reading : $!";
while(my $line=<$fh>) {
    push @lines, $line if ($line =~ $pattern);
}
close($fh);
print @lines;

2
你可以在while循环中简单地用'print $line'替换'push @lines, $line',避免使用任何数组。如果你的文件“很大”,那么总有一天会有人或某个东西触发grep命令,返回文件的几乎所有行。(同样巨大) - Randall

11

你可以在命令行上直接近似实现grep的原始版本。 -e 选项允许你在命令行上定义Perl脚本。 -n 选项大致包装了你的脚本,如下所示:while (<>){ SCRIPT }

perl -ne 'print if /PATTERN/' FILE1 FILE2 ...

稍微更好的 grep 近似方法是在每个匹配项前加上文件名前缀。需要注意的是,与上面的示例一样,此示例不必打开任何文件。相反,我们使用 Perl 的 <> 构造来迭代所有文件,$ARGV 变量提供当前文件名。

use strict;
use warnings;

my $pattern = shift;

while (my $line = <>){
    print $ARGV, ':', $line if $line =~ $pattern;
}

1
特殊变量$.包含当前行号。因此,如果您也想打印它,可以执行perl -ne 'print "$ARGV,$.:$_" if /PATTERN/' file1 file2 - hfs

4
基本的“grep”功能已经实现。(=~)
$string =~ /pattern/;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接