如何在本机 Perl 代码中复制 cat/sort/uniq?
How to replicate cat/sort/uniq in native Perl code?
我正在构建上一个问题中分享的知识:
Perl 脚本使用此代码:
my $cmd = "cat $TMPDIR/files.* | sort | uniq > $File"
`$cmd`
我正在尝试使用本机 Perl 将上述功能重建为 运行 在 MS Windows 上。到目前为止我有这个,但它不是很有效:
my $globPat = "$TMPDIR/parts.*"
my $outFile = "$TMPDIR/out.txt"
my %lines;
# 1) glob all files
while (my $glob = glob($globPat)) {
open(IN, "<", "$glob") or die("Can't read $glob");
# collect lines as unique keys in a hash
++$lines{ ($_)[1] } while <IN>;
close(IN);
}
# sort the key and save values to $glueFile
open(OUT, ">", "$outFile") or die("ERROR: Can't write $outFile");
foreach my $key (sort keys %lines) {
print OUT $lines{$key} . "\n";
}
close(OUT)
我在尝试解决问题时遇到了各种反复出现的错误(行号)。有人可以帮助解决 1) 如何正确使用 glob,2) 如何将从各种文件读取的行添加到一个散列键和 3) 对散列的键(行)进行排序并将它们打印到新的输出文件。
你可以用一行实现,然后用END
块来做排序,比如:
perl -ne '$h{ $_ } = 1; END { print sort keys %h }' $TMPDIR/files.*
List::MoreUtils::uniq
可以完成同名函数的工作。对于 cat
,我会简单地使用 <>
。当然,您应该知道那里有一个 "useless use of cat"。排序为 sort
.
use strict;
use warnings;
use List::MoreUtils qw(uniq);
my @list = uniq(<>);
my @sorted = sort @list;
print @sorted;
请注意,您不必在行中添加换行符,因为它们已经有一个。
如果您不想使用该模块,uniq
的代码相当简单,只需 copy/pasted.
sub uniq {
my %seen;
grep { not $seen{$_}++ } @_;
}
你的代码有几个问题
我假设您已经从类似 ++$lines{ (split)[1] }
的内容中推断出表达式 ++$lines{ ($_)[1] }
。但是有一个区别,因为 split
returns 一个 list 字段。 ($_)[1]
正试图从单元素列表中提取第二个元素。您只需要 ++$lines{$_}
在 print OUT $lines{$key}
中,您正在打印散列 %lines
的 值 。但它只是用作创建唯一列表的设备,值只是每一行在文件中出现的次数。你想要 keys,所以 print OUT $key, "\n"
是正确的
还有一些不良做法的实例不会阻止您的程序运行,但无论如何都应该修复。
局部变量只能使用小写字母、数字和下划线。大写字母保留用于全局标识符
您应该使用 词法 文件句柄,例如 open my $in_fh, ...
而不是 open IN, ...
。全局变量通常不是一个好主意,它也避免了 close
在其范围末尾的文件句柄的需要,因为它会自动发生
当 I/O 操作失败时,您应该 始终 将 $!
放入 die
字符串中。通常只使用 die $!
就足够了,因为输出包括源文件名和行号
最好使用 File::Spec::Functions
中的 catfile
而不是仅仅使用字符串连接。它可以正确处理多个路径分隔符之类的事情,并且阅读起来也更清晰
您不应该在裸变量周围加上引号。因此,例如,open(IN, "<", "$glob")
应该是 open(IN, "<", $glob)
。添加引号充其量不会有任何区别,最坏的情况是它会为您提供一个完全不同的字符串
这就是我重构你的程序的方式
use strict;
use warnings;
use File::Spec::Functions 'catfile';
my $temp_dir = '.';
my $glob_pat = catfile($temp_dir, 'parts.*');
my $out_file = catfile($temp_dir, 'out.txt');
my %lines;
while ( my $parts_file = glob($glob_pat) ) {
open my $in_fh, '<', $parts_file or die qq{Can't read "$parts_file": $!};
++$lines{$_} while <$in_fh>;
}
open my $out_fh, '>', $out_file or die qq{ERROR: Can't write to "$out_file": $!};
for my $line (sort keys %lines) {
print $out_fh $line, "\n";
}
close $out_fh;
您也可以这样使用glob
:
my @files = glob("$TMPDIR/parts.*");
foreach my $file (@files)
{
open my $fh, "<", $file or die "couldn't open '$file': $!";
while (<$fh>)
{
#do whatever you want to do;
}
}
我正在构建上一个问题中分享的知识:
Perl 脚本使用此代码:
my $cmd = "cat $TMPDIR/files.* | sort | uniq > $File"
`$cmd`
我正在尝试使用本机 Perl 将上述功能重建为 运行 在 MS Windows 上。到目前为止我有这个,但它不是很有效:
my $globPat = "$TMPDIR/parts.*"
my $outFile = "$TMPDIR/out.txt"
my %lines;
# 1) glob all files
while (my $glob = glob($globPat)) {
open(IN, "<", "$glob") or die("Can't read $glob");
# collect lines as unique keys in a hash
++$lines{ ($_)[1] } while <IN>;
close(IN);
}
# sort the key and save values to $glueFile
open(OUT, ">", "$outFile") or die("ERROR: Can't write $outFile");
foreach my $key (sort keys %lines) {
print OUT $lines{$key} . "\n";
}
close(OUT)
我在尝试解决问题时遇到了各种反复出现的错误(行号)。有人可以帮助解决 1) 如何正确使用 glob,2) 如何将从各种文件读取的行添加到一个散列键和 3) 对散列的键(行)进行排序并将它们打印到新的输出文件。
你可以用一行实现,然后用END
块来做排序,比如:
perl -ne '$h{ $_ } = 1; END { print sort keys %h }' $TMPDIR/files.*
List::MoreUtils::uniq
可以完成同名函数的工作。对于 cat
,我会简单地使用 <>
。当然,您应该知道那里有一个 "useless use of cat"。排序为 sort
.
use strict;
use warnings;
use List::MoreUtils qw(uniq);
my @list = uniq(<>);
my @sorted = sort @list;
print @sorted;
请注意,您不必在行中添加换行符,因为它们已经有一个。
如果您不想使用该模块,uniq
的代码相当简单,只需 copy/pasted.
sub uniq {
my %seen;
grep { not $seen{$_}++ } @_;
}
你的代码有几个问题
我假设您已经从类似
++$lines{ (split)[1] }
的内容中推断出表达式++$lines{ ($_)[1] }
。但是有一个区别,因为split
returns 一个 list 字段。($_)[1]
正试图从单元素列表中提取第二个元素。您只需要++$lines{$_}
在
print OUT $lines{$key}
中,您正在打印散列%lines
的 值 。但它只是用作创建唯一列表的设备,值只是每一行在文件中出现的次数。你想要 keys,所以print OUT $key, "\n"
是正确的
还有一些不良做法的实例不会阻止您的程序运行,但无论如何都应该修复。
局部变量只能使用小写字母、数字和下划线。大写字母保留用于全局标识符
您应该使用 词法 文件句柄,例如
open my $in_fh, ...
而不是open IN, ...
。全局变量通常不是一个好主意,它也避免了close
在其范围末尾的文件句柄的需要,因为它会自动发生当 I/O 操作失败时,您应该 始终 将
$!
放入die
字符串中。通常只使用die $!
就足够了,因为输出包括源文件名和行号最好使用
File::Spec::Functions
中的catfile
而不是仅仅使用字符串连接。它可以正确处理多个路径分隔符之类的事情,并且阅读起来也更清晰您不应该在裸变量周围加上引号。因此,例如,
open(IN, "<", "$glob")
应该是open(IN, "<", $glob)
。添加引号充其量不会有任何区别,最坏的情况是它会为您提供一个完全不同的字符串
这就是我重构你的程序的方式
use strict;
use warnings;
use File::Spec::Functions 'catfile';
my $temp_dir = '.';
my $glob_pat = catfile($temp_dir, 'parts.*');
my $out_file = catfile($temp_dir, 'out.txt');
my %lines;
while ( my $parts_file = glob($glob_pat) ) {
open my $in_fh, '<', $parts_file or die qq{Can't read "$parts_file": $!};
++$lines{$_} while <$in_fh>;
}
open my $out_fh, '>', $out_file or die qq{ERROR: Can't write to "$out_file": $!};
for my $line (sort keys %lines) {
print $out_fh $line, "\n";
}
close $out_fh;
您也可以这样使用glob
:
my @files = glob("$TMPDIR/parts.*");
foreach my $file (@files)
{
open my $fh, "<", $file or die "couldn't open '$file': $!";
while (<$fh>)
{
#do whatever you want to do;
}
}