如何在本机 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;
    }
}