perl 文本处理(特别是加载文件时)
perl text-processing (in particular when loading files)
在 shell 中加载文件和对列进行排序通常很容易,结合使用 grep
、cut
、sed
、awk
等。
但是,当我必须在 Perl 中执行此操作时,我经常会使用许多拆分、一个接一个、正则表达式来完成漫长而痛苦的事情,结果是脏代码,如下所示:
open $FH, "<", $file;
@file = <$FH>;
close $FH;
foreach $line (@file) {
( $foo, $bar, $some, $thing) = ( split(/,/, $line) )[3,8,9,15]
( $new_some ) = (split(/-/, $some))[2];
($new_foo = $foo) =~ s/xx//;
$uc_bar = uc($bar);
# and so on.....
}
难道没有更优雅的方式来做这些事情(拆分字段、替换模式等)?或者更 "quicker" 的方式(不一定优雅)?
还有没有办法在加载时只加载文件的所需部分(无需将所有内容都加载到内存中,但在加载之前进行过滤)?
优雅是主观的,但我至少可以回答您的一个问题,并提出一些可能会缩短或改进您的代码的建议。
"is there a way to load just the required part of the file at loading time" - 在您展示的代码中,我认为不需要将整个文件加载到内存中。逐行处理文件的典型模式,以及与 Perl 的 -n
and -p
switches 相同的模式,是这种模式:
open my $fh, '<', $file or die "$file: $!";
while (<$fh>) { # reads line into $_
my @fields = split; # splits $_ on whitespace, like awk
my ($foo, $bar, $some, $thing) = @fields[3,8,9,15];
...
}
close $fh;
我认为这相当优雅,但根据您所写的内容,我猜您是在将其与大约 100 个字符以内的管道命令的一行进行比较。 Perl 也可以做到这一点:正如评论已经提到的,看看开关 -n
, -p
, -a
, -F
, and -i
。如果您展示一些您想做的事情的具体示例,您可能会得到一些回复,说明如何使用 Perl 缩短它。
但是,如果您要执行更多操作,那么通常最好将其扩展为如上所示的脚本。恕我直言,将内容放入脚本中可为您提供更多功能:它不像命令行历史那样短暂,更易于扩展,并且更易于使用模块,您可以添加命令行选项、处理多个文件等。例如,通过以下代码片段,您可以获得 Text::CSV
的所有功能 - 支持引用、转义、多行字符串等
use Text::CSV;
my $csv = Text::CSV->new({binary=>1, auto_diag=>2, eol=>$/});
open my $fh, '<', $file or die "$file: $!";
while ( my $row = $csv->getline($fh) ) {
...
$csv->print(select, $row);
}
$csv->eof or $csv->error_diag;
close $fh;
您可能还想查看该模块的 csv
函数,它在一个简短的函数中提供了很多功能。如果你仍然认为 "painful" 和 "dirty" 就这些了,你更愿意用更少的代码来做事,那么您可以使用一些快捷方式,例如将整个文件放入内存,my $data = do { local (*ARGV, $/) = $file; <> };
,或者与 -i
命令行开关相同:
local ($^I, @ARGV) = ('.bak', $file);
while (<>) {
# s///; or @F=split; or whatever
print; # prints $_ back out
}
我喜欢 Perl 的一件事是它允许您以多种不同的方式表达自己 - 无论您是想拼凑一个非常短的脚本来处理一次性任务,还是编写一个大型 OO 项目, TIMTOWTDI
在 shell 中加载文件和对列进行排序通常很容易,结合使用 grep
、cut
、sed
、awk
等。
但是,当我必须在 Perl 中执行此操作时,我经常会使用许多拆分、一个接一个、正则表达式来完成漫长而痛苦的事情,结果是脏代码,如下所示:
open $FH, "<", $file;
@file = <$FH>;
close $FH;
foreach $line (@file) {
( $foo, $bar, $some, $thing) = ( split(/,/, $line) )[3,8,9,15]
( $new_some ) = (split(/-/, $some))[2];
($new_foo = $foo) =~ s/xx//;
$uc_bar = uc($bar);
# and so on.....
}
难道没有更优雅的方式来做这些事情(拆分字段、替换模式等)?或者更 "quicker" 的方式(不一定优雅)?
还有没有办法在加载时只加载文件的所需部分(无需将所有内容都加载到内存中,但在加载之前进行过滤)?
优雅是主观的,但我至少可以回答您的一个问题,并提出一些可能会缩短或改进您的代码的建议。
"is there a way to load just the required part of the file at loading time" - 在您展示的代码中,我认为不需要将整个文件加载到内存中。逐行处理文件的典型模式,以及与 Perl 的 -n
and -p
switches 相同的模式,是这种模式:
open my $fh, '<', $file or die "$file: $!";
while (<$fh>) { # reads line into $_
my @fields = split; # splits $_ on whitespace, like awk
my ($foo, $bar, $some, $thing) = @fields[3,8,9,15];
...
}
close $fh;
我认为这相当优雅,但根据您所写的内容,我猜您是在将其与大约 100 个字符以内的管道命令的一行进行比较。 Perl 也可以做到这一点:正如评论已经提到的,看看开关 -n
, -p
, -a
, -F
, and -i
。如果您展示一些您想做的事情的具体示例,您可能会得到一些回复,说明如何使用 Perl 缩短它。
但是,如果您要执行更多操作,那么通常最好将其扩展为如上所示的脚本。恕我直言,将内容放入脚本中可为您提供更多功能:它不像命令行历史那样短暂,更易于扩展,并且更易于使用模块,您可以添加命令行选项、处理多个文件等。例如,通过以下代码片段,您可以获得 Text::CSV
的所有功能 - 支持引用、转义、多行字符串等
use Text::CSV;
my $csv = Text::CSV->new({binary=>1, auto_diag=>2, eol=>$/});
open my $fh, '<', $file or die "$file: $!";
while ( my $row = $csv->getline($fh) ) {
...
$csv->print(select, $row);
}
$csv->eof or $csv->error_diag;
close $fh;
您可能还想查看该模块的 csv
函数,它在一个简短的函数中提供了很多功能。如果你仍然认为 "painful" 和 "dirty" 就这些了,你更愿意用更少的代码来做事,那么您可以使用一些快捷方式,例如将整个文件放入内存,my $data = do { local (*ARGV, $/) = $file; <> };
,或者与 -i
命令行开关相同:
local ($^I, @ARGV) = ('.bak', $file);
while (<>) {
# s///; or @F=split; or whatever
print; # prints $_ back out
}
我喜欢 Perl 的一件事是它允许您以多种不同的方式表达自己 - 无论您是想拼凑一个非常短的脚本来处理一次性任务,还是编写一个大型 OO 项目, TIMTOWTDI