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

Fir*_*ges -9 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
Run Code Online (Sandbox Code Playgroud)

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   
Run Code Online (Sandbox Code Playgroud)

所以输出必须是这样的

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   
Run Code Online (Sandbox Code Playgroud)

bri*_*foy 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

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

% paste W.txt Rs.txt
Run Code Online (Sandbox Code Playgroud)

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

% paste W-noheader.txt Rs.txt
Run Code Online (Sandbox Code Playgroud)

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

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

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


Unix大师使用子进程提供了这样的命令行.这是来自Reddit的Rhombold:

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

$ paste file1 <(tail -n +2 file2) >output
Run Code Online (Sandbox Code Playgroud)

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

$ paste <(tail -n +10 file1) <(tail -n +3 file2) <(tail -n +7 file3) >output
Run Code Online (Sandbox Code Playgroud)


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

% epaste -l 1,2,3 file1 file2 file3
Run Code Online (Sandbox Code Playgroud)

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

% epaste file1=1 file2=37 file3
Run Code Online (Sandbox Code Playgroud)

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

看看Perl Power Tools版本的来源paste,我发现我真的只需要改变一个地方.当它打开文件时,我需要将文件"快进"到正确的起始行.目前的代码有:

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

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

除了换行符之外,我有一个非贪婪的匹配任何字符,(.*?)然后是一个选项部分,用于查找等号,后跟一系列十进制数字(?: = ([0-9]+) )?,但仅限于最后\z.该/x让我散播出在模式微不足道制作空白.

如果我匹配的东西,$line有一个值.如果我不这样做,那$line就是undef.如果有什么东西,我只需要快进$line.我使用defined来检查它.

    if( defined $line ) {
        ...
    }
Run Code Online (Sandbox Code Playgroud)

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

为此,我可以看一下$.,最近读取的文件的当前行号(在perlvar中记录.注意"最近阅读".我没有读过我正在使用的文件,但我可以使用tell更改$.为我刚刚打开的文件句柄而不读取数据:

        tell( $fh[$i] );
        readline( $fh[$i] ) while $. < $line - 1
Run Code Online (Sandbox Code Playgroud)

James在blogs.perl.org评论说我可以使用文件句柄作为对象并完全避免使用特殊变量:

... $fh->input_line_number < $line - 1
Run Code Online (Sandbox Code Playgroud)

那是我的Perl 4显示出来的.请注意,您可能必须包含use FileHandlev5.12及更早版本的代码,因为直到v5.14才会将其添加为默认值.


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

为了继续支持特殊文件名-作为标准输入的名称,我需要稍微调整处理选项,这样它就不会认为=是一个选项(我在这里没有显示):

% epaste -=3 W.txt
Run Code Online (Sandbox Code Playgroud)

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

% epaste animals.txt=2 animals.txt=6 animals.txt=4
Run Code Online (Sandbox Code Playgroud)

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

% epaste W.txt=2 Rs.txt
Run Code Online (Sandbox Code Playgroud)

我为那些想要文件或修正错误的人制作了一个史诗要点,以修复我所犯的错误.

而且,这是当天的最终编程技巧:让其他人编写程序.:)