使用Perl将两个文件逐行连接在一起的不同方法有多少种?

5
假设file1看起来像这样:
bye bye hello thank you
而file2看起来像这样:
chao hola gracias
期望的输出是这样的:
bye bye chao hello hola thank you gracias
我已经想出了五种不同的方法来解决这个问题。但我认为一定有更多的方法,可能更简洁、更优雅,并且我希望能学到更酷的东西 :)
以下是我迄今为止尝试过的代码,基于我从之前问题的许多解决方案中学到的知识。此外,我正在尝试消化或内化我从Llama书中获得的知识。
代码1:
#!perl
use autodie;
use warnings;
use strict;

open my $file1,'<','c:/file1.txt';
open my $file2,'<','c:/file2.txt';

while(defined(my $line1 = <$file1>)
        and defined(my $line2 = <$file2>)){
    die "Files are different sizes!\n" unless eof(file1) == eof(file2);
    $line1 .= $line2;
    $line1 =~ s/\n/ /;
    print "$line1 \n";
}

代码2:

#!perl
use autodie;
use warnings;
use strict;

open my $file1,'<','c:/file1.txt';
my @file1 = <$file1>;

open my $file2,'<','c:/file2.txt';
my @file2 =<$file2>;

for (my $n=0; $n<=$#file1; $n++) {
    $file1[$n] .=$file2[$n];
    $file1[$n]=~s/\n/ /;
    print $file1[$n];
}

代码 3:

#!perl
use autodie;
use warnings;
use strict;

open my $file1,'<','c:/file1.txt';
open my $file2,'<','c:/file2.txt';

my %hash;

while(defined(my $line1 = <$file1>)
      and defined(my $line2 = <$file2>)) {
  chomp $line1;
  chomp $line2;
  my ($key, $val) = ($line1,$line2);
  $hash{$key} = $val;
}
print map { "$_ $hash{$_}\n" } sort keys %hash;

代码 4:

#!perl
use autodie;
use warnings;
use strict;

open my $file1,'<','c:/file1.txt';
open my $file2,'<','c:/file2.txt';

while(defined(my $line1 = <$file1>)
      and defined(my $line2 = <$file2>)) {
  $line1 =~ s/(.+)/$1 $line2/;
  print $line1;
}

代码 5:

#!perl
use autodie;
use warnings;
use strict;

open my $file1,'<','c:/file1.txt';
my @file1 =<$file1>;

open my $file2,'<','c:/file2.txt';
my @file2 =<$file2>;

while ((@file1) && (@file2)){ 
    my $m = shift (@file1);
    chomp($m);

    my $n = shift (@file2);
    chomp($n);

    $m .=" ".$n;
    print "$m \n";
}

我尝试过类似这样的方法:
foreach $file1 (@file2) && foreach $file2 (@file2) {...}

但是Perl给了我一个语法错误警告。我很沮丧。但是我们可以同时运行两个foreach循环吗?

感谢您一如既往的评论、建议和慷慨的代码分享 :)


1
你最近不是发过一个几乎相同的问题吗? - user181548
2
事实上,这听起来像是一种没有正确答案的投票类型问题,因此可能应该是 CW。 - Sinan Ünür
1
@Kinopiko,我几天前问的那个问题“如何使用Perl将一个文件的一列替换为另一个文件的一列?”比这个问题更难。 - Mike
@Kinopiko,嗯,我想它们确实有一些相似之处,但它们完全不同。 - Mike
5
这是Perl语言。我总觉得,如果只有一种方法可以做某件事,那么我应该提交一个错误报告。 - David Thornley
显示剩余6条评论
5个回答

12

对于任意数量的文件都适用:

use strict;
use warnings;
use autodie;

my @handles = map { open my $h, '<', $_; $h } @ARGV;

while (@handles){
    @handles = grep { ! eof $_ } @handles;
    my @lines = map { my $v = <$_>; chomp $v; $v } @handles;
    print join(' ', @lines), "\n";
}

close $_ for @handles;

顺便说一句,我发现 my @handles = map { open my $h, '<', $_; $h } @ARGV; 更可取。你可以摆脱 @files 数组,因为你在其他地方没有使用它。我希望我能多次点赞你的答案。 - Sinan Ünür
看起来 @ARGV = ("c:/f1.txt","c:/f2.txt"); 比 open my $file1,'<','c:/f1.txt'; open my $file2,'<','c:/f2.txt'; 更简洁。 - Mike
@Mike 这里有另一种方法:my %d; push @{$d{$ARGV}}, $_ while <>。这将把所有文件读入到一个哈希数组引用中。打印输出类似于我的答案,但是你需要在列表为空时删除哈希键,而不是在进行修剪。 - FMc

12

最优雅的方法根本不需要使用perl

paste -d' ' file1 file2

2
+1 同意,但我认为原帖的目的是通过编写此类玩具程序来学习 Perl。 - Sinan Ünür
@mouviciel,这看起来不像是Perl。但我同意它确实很简洁 :) - Mike
我对Perl不够了解,无法用一行代码完成那个任务。也许可以参考SanHolo的答案来实现。 - mouviciel
1
@mouviciel:请参考https://dev59.com/MUrSa4cB1Zd3GeqPSwSy#1637438。但是,请**不要使用它**。 - Sinan Ünür
4
在Perl中:system("paste -d' ' $file1 $file2")的意思是运行一个名为“paste”的命令,使用空格作为分隔符将$file1和$file2文件的内容在列方向上合并输出。 - mob

7

如果我是一个打高尔夫球的人,我可以将@FM的答案改写为:

($,,$\)=(' ',"\n");@_=@ARGV;open $_,$_ for @_;print
map{chomp($a=<$_>);$a} @_=grep{!eof $_} @_ while @_

以下是翻译:

这可能是一个一行代码的解决方案,但那只是太过于邪恶了。;-)

好了,这里有不到100个字符的代码:

C:\Temp> perl -le "$,=' ';@_=@ARGV;open $_,$_ for @_;print map{chomp($a =<$_>);$a} @_=grep{!eof $_ }@_ while @_" file1 file2

如果可以使用“ slurp ”(为什么不呢——我们确实正在寻找不同的方法),我认为我已经发现了这条路的疯狂之处:

@_=@ARGV;chomp($x[$.-1]{$ARGV}=$_) && eof
and $.=0 while<>;print "@$_{@_}\n" for @x

C:\Temp> perl -e "@_=@ARGV;chomp($x[$.-1]{$ARGV}=$_) && eof and $.=0 while<>;print qq{@$_{@_}\n} for @x" file1 file2

输出结果:

再见拜拜
你好 哈啰
谢谢 谢谢

@Sinan :) 非常感谢!这个惊人的一行代码非常好用:perl -le "$,=' ';@=@ARGV;open $,$_ for @;print map{chomp($a =<$_>);$a} @=grep{!eof $_ }@_ while @_" "c:/file1.txt" "c:/file2.txt"。 - Mike
@Sinan,这一行代码不起作用:perl -e "@=@ARGV;chomp $x[$.-1]{$ARGV}=$ && eof and $.=0 while<>;print qq{@${@}\n} for @x" "c:/f1.txt" "c:/f2.txt"。它给我返回了类似于“Can't modify scalar chomp in scalar assignment at -e line 1, near "eof and" Execution of -e aborted due to compilation errors”的错误信息。 - Mike
@Mike 我需要在 chomp 的周围加上括号。现在应该没问题了。 - Sinan Ünür

2
一个简单的代码,只有最基本的错误检查:
#!/usr/bin/perl -w

use strict;

open FILE1, '<file1.txt';
open FILE2, '<file2.txt';

while (defined(my $one = <FILE1>) or defined(my $twotemp = <FILE2>)){
    my $two = $twotemp ? $twotemp : <FILE2>;
    chomp $one if ($one);
    chomp $two if ($two);
    print ''.($one ? "$one " : '').($two ? $two : '')."\n";
}

不,你不能在同一个线程中同时运行两个循环,你需要使用 fork,但这并不能保证同步运行。


1
没有经过测试,我就能够识别出我期望的三元运算符。哇,这很酷。我自己尝试了几次使用三元运算符来完成工作,但都没有成功。非常感谢 :) - Mike
如果输入行是数字零,则无法工作。在条件运算符中使用“defined”。 - Rob Kennedy
是的,Rob,没错。(好吧,我说它会将第一个文件的每一行连接起来,所以这种行为可能有点正确;))。 但这就是我所说的最小错误检查 - 应该进行更多的错误检查。 - Pascal
编辑:好的,现在已经调整为无论这两个文件有多长,都可以将它们的结尾读取完整。 - Pascal
不,我的意思是如果一行是数字零“0”,那么你的脚本将不会在输出中包含该字符。我不是在谈论空字符或空行,只是键盘顶部的普通数字字符。我也不是在谈论不同长度的文件或错误检查。 - Rob Kennedy
哦,好的,我明白了!我从来没有遇到过这个问题,但你显然是对的。我应该从现在开始习惯使用defined(),谢谢。 - Pascal

2

一个比你的代码5更简单的替代方案,它允许任意数量的行,并且不在乎文件是否有不同数量的行(致谢@FM):

#!/usr/bin/perl

use strict; use warnings;

use File::Slurp;
use List::AllUtils qw( each_arrayref );

my @lines = map [ read_file $_ ], @ARGV;

my $it = each_arrayref @lines;

while ( my @lines = grep { defined and chomp and length } $it->() ) {
    print join(' ', @lines), "\n";
}

同时,不使用任何外部模块:

#!perl
use autodie; use warnings; use strict;

my ($file1, $file2) = @ARGV;

open my $file1_h,'<', $file1;
my @file1 = grep { chomp; length } <$file1_h>;

open my $file2_h,'<', $file2;
my @file2 =  grep { chomp; length } <$file2_h>;

my $n_lines = @file1 > @file2 ? @file1 : @file2;

for my $i (0 .. $n_lines - 1) {
    my ($line1, $line2) = map {
        defined $_ ? $_ : ''
    } $file1[$i], $file2[$i];
    print $line1, ' ', $line2, "\n";
}

如果您想连接仅出现在两个文件中的行:

#!perl
use autodie; use warnings; use strict;

my ($file1, $file2) = @ARGV;

open my $file1_h,'<', $file1;
my @file1 = grep { chomp; length } <$file1_h>;

open my $file2_h,'<', $file2;
my @file2 =  grep { chomp; length } <$file2_h>;

my $n_lines = @file1 < @file2 ? @file1 : @file2;

for my $i (0 .. $n_lines - 1) {
    print $file1[$i], ' ', $file2[$i], "\n";
}

测试失败。Perl显示“在@INC中找不到List/Allutils.pm”。但我会获取该模块并再次进行测试。 - Mike
1
+1 对于 read_fileeach_array。我也考虑过这种方法,但是最后一刻注意到了你的答案。一个人很容易将其泛化以处理任意数量的文件。 - FMc
@Mike,这似乎是一个单独的问题,但我认为要么之前安装的 perl.exe 仍在您的路径中,要么存在某种文件关联问题。ftype Perl 命令输出什么? - Sinan Ünür
C:/ftype Perl 给了我这个:perl="C:\Perl\bin\perl.exe" "%1" %* - Mike
@Sinan,对于这个微小的工作,我们可以使用相当令人印象深刻的Perl运算符和函数。这是很好的。 - Mike
显示剩余5条评论

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