如何使用Perl将文件的元素作为列添加到第二个文件?

时间:2014-01-14 17:58:41

标签: perl

第一个名为W.txt的文件和2sd是Rs.txt

W.txt:

ID  age gender  bmi status  
CAD7    57  F   28.80   0
CAD9    74  F   29.26   1
CAD11   53  M   NA  1
CAD12   61  M   27.16   1
CAD14   77  M   29.28   1
CAD17   74  M   35.99   1
CAD18   81  F   28.12   1
CAD24   73  M   22.23   1

Rs.txt:

2   2   2   
2   2   2   
2   0   2   
2   2   2   
1   2   2   
1   2   2   
1   2   2   
1   2   2   

所以输出必须像这样

CAD7    57  F   28.80   0   2   2   2   
CAD9    74  F   29.26   1   2   2   2
CAD11   53  M   NA  1   1   2   2   

3 个答案:

答案 0 :(得分:22)

我认为您正在尝试合并两个具有相应记录的文件。我使用遗留系统多次看到这个问题,其中不同的数据来自不同的来源。你必须确保所有记录排成一行(例如,在一个列表中没有添加或删除),但我们现在认为这是真的。

如果您习惯于处理面向行的文件(而不是宇宙中的所有内容),那么这是一项简单的任务。你从每个文件读取一行,删除行结束,连接两行,并将结果输出到第三个文件(虽然在这种情况下我使用标准输出):

#!/usr/bin/perl
use strict; # a programming aid to keep us honest

# open each file
open my $W, '<', 'W.txt' or die "Could not open W.txt: $!";
open my $Rs, '<', 'Rs.txt' or die "Could not open Rs.txt: $!";

# read the header of W.txt and ignore it
# this syncs the positions in the file
readline( $W );

while( 1 ) { # keep going until something else stops us
    # read a line for each file
    my $W_line  = readline( $W  );
    my $Rs_line = readline( $Rs );

    # stop if we ran out of lines from one of the files
    last unless( defined $W_line and defined $Rs_line );

    # remove the line ending from the W line
    # leave the line ending on the Rs line because we'll use it
    chomp( $W_line );

    # output the combined line with a space between them
    print $W_line, ' ', $Rs_line;
    }

我在这里添加了大量代码评论。当我处理一些我不确定的事情时,我经常在评论中勾勒出我想要的过程,然后填写代码来执行这些操作。如果您手动执行此操作,这大致就是您可能采取的过程。请记住,编程是使我们需要很长时间才能完成的无聊任务的自动化,因此步骤通常是相同的。实际上,有时我会手工做事,以便弄清楚过程中的问题。

但是,编程的真正诀窍是知道什么时候你根本不需要编程。您想要合并两个文件。有一个程序:

% paste W.txt Rs.txt

W.txt 中的标题行存在问题。最简单的方法可能是简单地复制文件并删除那一行。如果您不必再次这样做,那么很少的人工干预可以为您节省大量工作:

% paste W-noheader.txt Rs.txt

或者,你可以在 Rs.txt 中添加一条虚线,这样它也有一个标题。您可能能够获取该数据的来源以添加该数据。为新值添加列标题会更好。另一个编程技巧是啤酒的应用。它润滑了许多问题。

如果你不在拥有paste的机器上(我没有看着你,Windows,但我认真对待),那就是一个名为{{{}的神话般的项目。 3}}重新创建Perl中的工具,这意味着您可以在任何有perl的地方使用它们,但也可以查看源代码以了解它们是如何做到的。您可以使用接近您想要的工具,并根据您的本地目的稍微修改它。 Perl在这里没什么特别的。如果你发现任何语言都很接近,那就去吧。诀窍是完成工作。

但是,我们假设您既不能手动编辑文件来删除标题(可能因为这必须是可重复的),也不能更改源以添加标题。您需要从不同的行开始同步文件。我认为paste应该处理这个问题,但我找不到任何版本,我也认为tailhead的应用很棘手。也许一个更好的Unix大师可以提供一个命令行。


Unix大师使用子进程提供了这样的命令行。 Perl Power Tools

要使用file2的内容粘贴file1减去第一行,您可以执行以下操作:

$ paste file1 <(tail -n +2 file2) >output

您可以将此概括为任意数量的输入:

$ paste <(tail -n +10 file1) <(tail -n +3 file2) <(tail -n +7 file3) >output


我已经给你完成任务的答案了,所以现在我要解决这个问题了。我想要一个改进的粘贴,让我指定每个文件的起始行。首先,我需要知道如何指定它。 paste可以使用两个或更多文件,因此我希望能够这样做。我需要能够为每个文件指定起始行号。我可以做这样的事情,我有一个起始行号列表,其顺序与我指定的文件相同。在这种情况下,逗号不是参数分隔符:

% epaste -l 1,2,3 file1 file2 file3

我不喜欢这样,因为行号与文件分开;这对我来说似乎很脏我宁愿把它们放在一起。如果我必须从另一个程序构建此命令行,我不想跟踪行号并等到每个输入结束以知道如何输出命令。相反,通过允许文件名以&#34; = N&#34;结束,我会做一些感觉有点脏的事情。指定起始行:

% epaste file1=1 file2=37 file3

对于名称中包含=的文件存在问题,但生活艰难。

查看This is from Rhombold on Reddit的来源,我发现其中只有一个我需要改变的地方。当它打开文件时,我需要&#34;快进&#34;文件到正确的起始行。目前的代码有:

for $i (0..$#ARGV) {
    $fh[$i] = "F$i";
    open($fh[$i], $ARGV[$i]) or die "$0: cannot open $ARGV[$i]";
}

但我需要更改它来解析文件名以查找起始行号然后移动到该行号。

for $i (0..$#ARGV) {
    $fh[$i] = "F$i";
    my( $name, $line ) = $ARGV[$i] =~ /(.*?) (?: = ([0-9]+) )? \z/x;
    open($fh[$i], $name) or die "$0: cannot open $name";
    if( defined $line ) {
        tell( $fh[$i] );
        readline( $fh[$i] ) while $. < $line - 1
    }
}

这里有一些有趣的事情需要注意。在获取文件名的行中,我有这个匹配:

$ARGV[$i] =~ /(.*?) (?: = ([0-9]+) )? \z/x;

我有一个非贪婪的匹配任何字符,除了换行符(.*?)后跟一个选项部分,以查找等号后跟一系列小数位(?: = ([0-9]+) )?,但仅限于结束\z/x让我通过使模式中的空白无关紧要来传播它。

如果我匹配某些内容,则$line会有值。如果我不这样做,$line有undef。如果$line中存在某些内容,我只需要快进。我使用Perl Power Tools version of paste来检查。

    if( defined $line ) {
        ...
    }

if内,我需要停在正确的行号。如果我想从第37行开始,我需要阅读并丢弃36行。这比我指定的数字少一个。

为此,我可以查看$.,即最近阅读的文件的当前行号(记录在defined中。请注意&#34;最近阅读&#34;。我我还没有读过我正在使用的文件,但我可以使用perlvar$.更改为刚打开的文件句柄,而无需读取数据:

        tell( $fh[$i] );
        readline( $fh[$i] ) while $. < $line - 1

詹姆斯在tell评论我可以blogs.perl.org并完全避免使用特殊变量:

... $fh->input_line_number < $line - 1

那是我的Perl 4显示出来的。请注意,您可能必须在v5.12及更早版本的代码中包含use FileHandle,因为在v5.14之前未将其添加为默认值。


几乎是这样的。我不会看看程序的其余部分以及处理paste的其他功能所做的棘手事情,例如更改分隔符。

要继续支持特殊文件名-作为标准输入的名称,我需要稍微调整处理选项,以便它不会认为=是一个选项(我不喜欢#39; t显示在这里):

% epaste -=3 W.txt

我希望我可以多次指定-一个起始行号,但这些都是相互依赖的,因为它们使用相同的数据。我可以同时指定多个文件(如果您的文件系统允许同时读取文件):

% epaste animals.txt=2 animals.txt=6 animals.txt=4

这意味着您的解决方案归结为:

% epaste W.txt=2 Rs.txt

我已经为那些想要该文件或进行更正的人制作了used the filehandle as an object来修复我所犯的错误。

而且,那是当天最后的编程技巧:让其他人来编写程序。 :)

答案 1 :(得分:-1)

仅仅要求代码是非常糟糕的举止!

你可以通过将整个文件读成字符串并将字符串分割为"\n",将其推入数组并打印每个元素来实现:

#!/usr/bin/perl

use strict;
use warnings;

open W_FILE, "./W.txt" or die $!;
open R_FILE, "./R.txt" or die $!; 
my $w_content;
my $r_content;

while(<W_FILE>)
{
    $w_content .= $_;
}
close(W_FILE);
while(<R_FILE>)
{
    $r_content .= $_;
}
close(R_FILE);

my @w_array = split(/\n/, $w_content);
my @r_array = split(/\n/, $r_content);

my $i;
for($i=0;$i<$#w_array; $i++)
{
    print $w_array[$i+1]." ".$r_array[$i]."\n";
}

答案 2 :(得分:-1)

假设索引值是唯一的并且数据适合内存,我只使用数组哈希

use strict;
use warnings;

my $data_hash_ref;  #store the data here

open (my $w_fh, "<", "W.txt") or die $!;

#skip title line
my $line = <$w_fh>;

while ($line = <$w_fh>) {
    chomp $line;
    my @cols = split ("\t", $line);
    my $key = shift (@cols);
    $data_hash_ref -> {$key} = \@cols;
}
close $w_fh;

open (my $rs_fh, "<", "Rs.txt") or die $!;

while ($line = <$Rs_fh>) {
    chomp $line;
    my @cols = split ("\t", $line);
    my $key = shift (@cols);

    #You probably want to check if the key exists first and handle it if it doesn't,
    #but I'm skipping that here

    push (@{$data_hash_ref -> {$key}}, @cols);
}
close $rs_fh;

#print it out
open (my $out_fh, ">", "merged.txt") or die $!;

foreach my $key (sort keys %$data_hash_ref) {
    my $row = join ("\t", @{$data_hash_ref -> {$key}})
    print $out_fh "$key\t$row\n";
}
close $out_fh;

不如某些解决方案那么优雅,但如果您了解perl引用/解除引用,则更容易理解,更重要的是,如果除了打印之外还想对数据执行任何其他操作,则易于操作。