【问题标题】:Perl : Adding 2 files line by linePerl:逐行添加2个文件
【发布时间】:2016-07-02 23:54:08
【问题描述】:

我是 perl 的初学者,所以请多多包涵。

我有 2 个文件:

1
2
3

2
4
5
6

我想创建一个新文件,它是上述 2 个文件的总和:

输出文件:

3
6
8
6

我现在正在做的是将文件作为数组读取并逐个元素地添加它们。

要添加我正在使用的数组:

$asum[@asum] = $array1[@asum] + $array2[@asum] while defined $array1[@asum] or defined $array2[@asum];

但这给出了以下错误:

Argument "M-oM-;M-?3" isn't numeric in addition (+) at perl_ii.pl line 30.
Argument "M-oM-;M-?1" isn't numeric in addition (+) at perl_ii.pl line 30.
Use of uninitialized value in addition (+) at perl_ii.pl line 30.

我正在使用以下代码将文件作为数组读取:

use strict;
use warnings;

my @array1;
open(my $fh, "<", "file1.txt") or die "Failed to open file1\n";
while(<$fh>) { 
    chomp; 
    push @array1, $_;
} 
close $fh;

my @array2;
open(my $fh1, "<", "file2.txt") or die "Failed to open file2\n";
while(<$fh1>) {
    chomp;
    push @array2, $_;
}
close $fh1 ;

谁能告诉我如何解决这个问题,或者提供更好的方法?

【问题讨论】:

    标签: perl file


    【解决方案1】:

    这是另一个使用菱形文件读取运算符&lt;&gt; 的 Perl 解决方案。这会读入命令行上指定的文件(而不是在程序中显式打开它们)。抱歉,我找不到解释这一点的文档部分。

    此程序的命令行如下所示:

    perl myprogram.pl file1 file2 &gt; outputfile

    其中file1和file2是2个输入文件,outputfile是要打印相加结果的文件。

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my @sums;
    my $i = 0;
    while (my $num = <>) {
        $sums[$i++] += $num;
        $i = 0 if eof;
    }
    
    print "$_\n" for @sums;
    

    注意:$i 在文件末尾被重置为零,(在这种情况下,在读取第一个文件之后)。实际上,在读取第二个文件后它也被重置为 0。但是,这对程序没有影响,因为在您的示例中的第二个文件之后没有要读取的文件。

    【讨论】:

    • 非常感谢!似乎是我的问题的更短的解决方案:)
    • 这是对在内存中保存所有文件的改进,但它的内存占用仍然由最大文件的大小决定。见How to sum data from multiple files in Perl?
    【解决方案2】:

    以下解决方案使程序的内存占用与文件的大小无关。相反,现在内存占用仅取决于文件的数量

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    use Carp qw( croak );
    use List::Util qw( sum );
    use Path::Tiny;
    
    run(@ARGV);
    
    sub run {
        my @readers = map make_reader($_), @_;
    
        while (my @obs = grep defined, map $_->(), @readers) {
            print sum(@obs), "\n";
        }
    
        return;
    }
    
    sub make_reader {
        my $fname = shift;
        my $fhandle = path( $fname )->openr;
        my $is_readable = 1;
        sub {
            return unless $is_readable;
    
            my $line = <$fhandle>;
            return $line if defined $line;
    
            close $fhandle
                or croak "Cannot close '$fname': $!";
    
            $is_readable = 0;
            return;
        }
    }
    

    【讨论】:

    • 谢谢,思南。我已将您的程序添加到我保存的程序中。我已经看到需要一次读取 2 个或更多文件一行然后处理它们的其他问题。当我在这里提供我的解决方案时,我没有考虑可能的内存问题。只是想知道 - 你为什么使用 Carp 模块中的 croak 而不是 die。构建模块时通常使用 Carp 并且您想使用 croak 吗?我从来没有被告知何时使用鲤鱼而不是死亡。我看到您编辑了您的 make_reader 子 - 猜它与原始定义相同。
    • croak 的主要好处是您可以在不更改脚本的情况下强制使用stack dump。我编辑了我的答案以避免if/else。我认为线性流读起来要好得多。我已经写了无数的变体,其中一些带有日志记录、更好的错误恢复、格式化等,但我试图避免所有这些,以便在这里提炼所有内容。
    • 哇,这是一个宝库。很好的使用闭包,在现实生活中很少见。谢谢。
    • @SinanÜnür 你提到了其他版本的代码具有更好的错误恢复(我假设错误处理)。我发现您在close 上检查错误而不是在&lt;&gt; 上检查错误是不一致的。我不知道检查open 上的错误,因为它取决于pathreadr 的错误处理。您是否考虑修改您的答案以忽略close 错误,或者最好为&lt;&gt; 添加错误处理?
    • @JohnWiersba 我所指的错误处理是关于检查输入是否符合规范。快速浏览Path::Tiny 会显示,openr 会在失败时发牢骚。至于来自readline 的错误,请参阅perldoc -f readline:“当您从不信任的文件句柄(例如tty 或套接字)中读取时,检查$! 会很有帮助”。在正常情况下,检查readline 中的错误是相当多余的。在这种情况下,如果readline 返回undef,我们立即尝试close 文件。如果出了什么问题,那会告诉我们的。
    【解决方案3】:

    您的脚本现在有两个不同的问题:

    1. 第一个错误

      参数 "M-oM-;M-?3" 在 perl_ii.pl 行不是数字加法 (+) 30

      发生是因为您的输入文件以 Unicode 格式保存,并且第一行使用“\xFF\xFE”BOM 字节读取。 为了简单地修复它,只需将文件重新保存为 ANSI 文本。如果需要 Unicode,则从您从文件中读取的第一个字符串中删除这些字节。

    2. 第二个错误

      在 perl_ii.pl 第 30 行使用未初始化的值加法 (+)。

      发生是因为您访问了第一个不存在的数组中的第 4 个元素。请记住,您选择最大输入数组长度作为索引限制。要修复它,只需为输入元素添加以下条件:

      $asum[@asum] = (@asum < @array1 ? $array1[@asum] : 0)  + (@asum < @array2 ? $array2[@asum] : 0) while defined $array1[@asum] or defined $array2[@asum];
      

    【讨论】:

      【解决方案4】:

      读取你的两个文件的逻辑是一样的,我建议使用一个子程序并调用它两次:

      #!/usr/bin/env perl
      
      use strict;
      use warnings;
      
      my @array1 = read_into_array('file1.txt');
      my @array2 = read_into_array('file2.txt');
      
      sub read_into_array
      {
          my $filename = shift;
          my @array;
          open(my $fh, "<", $filename) or die "Failed to open $filename: $!\n";
          while(<$fh>) { 
              chomp; 
              push @array, $_;
          } 
          close $fh;
          return @array;
      }
      

      但这只是我的观察,并不能解决您的问题。作为CodeFuller already said,您应该将文件重新保存为纯 ASCII 而不是 UTF-8。

      第二个问题,使用未初始化的值加法 (+),也可以通过在 Perl 5.10 中引入的Logical Defined Or 运算符// 来解决:

      my @asum;
      $asum[@asum] = ($array1[@asum] // 0) 
                   + ($array2[@asum] // 0) 
                   while defined $array1[@asum] or defined $array2[@asum];
      

      不,这不是评论,而是与|| 非常相似的运算符。不同之处在于它在左侧(lhs)为undef 时触发,而|| 在lhs 为falsy 时触发(即0'' 或@987654331 @)。因此

      $array1[@asum] // 0
      

      如果 $array1[@asum] 是 undef,则给出 0。是一样的

      defined($array1[@asum]) ? $array1[@asum] : 0
      

      【讨论】:

      • 感谢您的帮助!
      【解决方案5】:

      完全不同的方法:

      $ paste -d '+' file1 file2 | sed 's/^+//;s/+$//' | bc
      3
      6
      8
      6
      

      paste 命令将文件彼此相邻打印,以+ 符号分隔:

      $ paste -d '+' file1 file2
      1+2
      2+4
      3+5
      +6
      

      sed 命令删除前导和尾随 + 符号,因为它们会绊倒 bc

      $ paste -d '+' file1 file2 | sed 's/^+//;s/+$//'
      1+2
      2+4
      3+5
      6
      

      bc 最终计算总和。

      【讨论】:

      • 谢谢!但我一直在寻找专门在 Perl 中的解决方案。 :)
      【解决方案6】:

      这是Sinan’s approach 的更 Perlish 形式的再现:

      #!/usr/bin/env perl
      use 5.010; use strict; use warnings;
      
      use autodie;
      use List::Util 'sum';
      
      my @fh = map { open my $fh, '<', $_; $fh } @ARGV;
      
      while ( my @value = grep { defined } map { scalar readline $_ } @fh ) {
          say sum @value;
          @fh = grep { not eof $_ } @fh if @value < @fh;
      }
      

      【讨论】:

        猜你喜欢
        • 2013-03-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-22
        • 2011-11-08
        相关资源
        最近更新 更多