第一个名为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
答案 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
应该处理这个问题,但我找不到任何版本,我也认为tail
或head
的应用很棘手。也许一个更好的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引用/解除引用,则更容易理解,更重要的是,如果除了打印之外还想对数据执行任何其他操作,则易于操作。