我有一个标签分隔的文本文件。它们可以达到1 GB。我将根据其中的样本数量具有可变数量的列。每个样本有八列。例如,sampleA:ID1,id2,MIN_A,AVG_A,MAX_A,AR1_A,AR2_A,AR_A,AR_5。其中ID1和id2是所有样本的共同点。我想要实现的是根据样本数将整个文件拆分成文件块。
ID1,ID2,MIN_A,AVG_A,MAX_A,AR1_A,AR2_A,AR3_A,AR4_A,AR5_A,MIN_B, AVG_B, MAX_B,AR1_B,AR2_B,AR3_B,AR4_B,AR5_B,MIN_C,AVG_C,MAX_C,AR1_C,AR2_C,AR3_C,AR4_C,AR5_C
12,134,3535,4545,5656,5656,7675,67567,57758,875,8678,578,57856785,85587,574,56745,567356,675489,573586,5867,576384,75486,587345,34573,45485,5447
454385,3457,485784,5673489,5658,567845,575867,45785,7568,43853,457328,3457385,567438,5678934,56845,567348,58567,548948,58649,5839,546847,458274,758345,4572384,4758475,47487
这是我的模型文件的外观,我希望将它们作为:
File A :
ID1,ID2,MIN_A,AVG_A,MAX_A,AR1_A,AR2_A,AR3_A,AR4_A,AR5_A
12,134,3535,4545,5656,5656,7675,67567,57758,875
454385,3457,485784,5673489,5658,567845,575867,45785,7568,43853
File B:
ID1, ID2,MIN_B, AVG_B, MAX_B,AR1_B,AR2_B,AR3_B,AR4_B,AR5_B
12,134,8678,578,57856785,85587,574,56745,567356,675489
454385,3457,457328,3457385,567438,5678934,56845,567348,58567,548948
File C:
ID1, ID2,MIN_C,AVG_C,MAX_C,AR1_C,AR2_C,AR3_C,AR4_C,AR5_C
12,134,573586,5867,576384,75486,587345,34573,45485,5447
454385,3457,58649,5839,546847,458274,758345,4572384,4758475,47487.
有没有简单的方法可以做到这一点,而不是通过一个阵列?
我如何计算出我的逻辑是计算(标题数 - 2)并将它们除以8将得到文件中的样本数。然后遍历数组中的每个元素并解析它们。这样做似乎是一种乏味的方式。我很乐意知道任何更简单的处理方法。
由于 SIPRA
答案 0 :(得分:8)
#!/bin/env perl
use strict;
use warnings;
# open three output filehandles
my %fh;
for (qw[A B C]) {
open $fh{$_}, '>', "file$_" or die $!;
}
# open input
open my $in, '<', 'somefile' or die $!;
# read the header line. there are no doubt ways to parse this to
# work out what the rest of the program should do.
<$in>;
while (<$in>) {
chomp;
my @data = split /,/;
print $fh{A} join(',', @data[0 .. 9]), "\n";
print $fh{B} join(',', @data[0, 1, 10 .. 17]), "\n";
print $fh{C} join(',', @data[0, 1, 18 .. $#data]), "\n";
}
更新:我感到无聊并且变得更加聪明,因此它会自动处理文件中的任意数量的8列记录。不幸的是,我没有时间解释它或添加评论。
#!/usr/bin/env perl
use strict;
use warnings;
# open input
open my $in, '<', 'somefile' or die $!;
chomp(my $head = <$in>);
my @cols = split/,/, $head;
die 'Invalid number of records - ' . @cols . "\n"
if (@cols -2) % 8;
my @files;
my $name = 'A';
foreach (1 .. (@cols - 2) / 8) {
my %desc;
$desc{start_col} = (($_ - 1) * 8) + 2;
$desc{end_col} = $desc{start_col} + 7;
open $desc{fh}, '>', 'file' . $name++ or die $!;
print {$desc{fh}} join(',', @cols[0,1],
@cols[$desc{start_col} .. $desc{end_col}]),
"\n";
push @files, \%desc;
}
while (<$in>) {
chomp;
my @data = split /,/;
foreach my $f (@files) {
print {$f->{fh}} join(',', @data[0,1],
@data[$f->{start_col} .. $f->{end_col}]),
"\n";
}
}
答案 1 :(得分:2)
这与样本数量无关。我对输出文件名不太自信,因为你可能会达到超过26个样本。如果是这种情况,只需替换输出文件名的工作方式即可。 :)
use strict;
use warnings;
use File::Slurp;
use Text::CSV_XS;
use Carp qw( croak );
#I'm lazy
my @source_file = read_file('source_file.csv');
# you metion yours is tab separated
# just add the {sep_char => "\t"} inside new
my $csv = Text::CSV_XS->new()
or croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
my $output_file;
#read each row
while ( my $raw_line = shift @source_file ) {
$csv->parse($raw_line);
my @fields = $csv->fields();
#get the first 2 ids
my @ids = splice @fields, 0, 2;
my $group = 0;
while (@fields) {
#get the first 8 columns
my @columns = splice @fields, 0, 8;
#if you want to change the separator of the output replace ',' with "\t"
push @{ $output_file->[$group] }, (join ',', @ids, @columns), $/;
$group++;
}
}
#for filename purposes
my $letter = 65;
foreach my $data (@$output_file) {
my $output_filename = sprintf( 'SAMPLE_%c.csv', $letter );
write_file( $output_filename, @$data );
$letter++;
}
#if you reach more than 26 samples then you might want to use numbers instead
#my $sample_number = 1;
#foreach my $data (@$output_file) {
# my $output_filename = sprintf( 'sample_%s.csv', $sample_number );
# write_file( $output_filename, @$data );
# $sample_number++;
#}
答案 2 :(得分:0)
您说制表符已分隔,但您的示例显示它以逗号分隔。我认为将样本数据放入Markdown是一个限制吗?
我猜你有点关心内存,所以你要打开多个文件并在解析你的大文件时写下它们。
我想试试Text::CSV::Simple。但是,我相信它会将整个文件读入内存,这对于这么大的文件来说可能是一个问题。
读取一行非常容易,并将该行放入列表中。问题是将该列表中的字段映射到字段本身的名称。
如果您读入带有while
循环的文件,则不会立即将整个文件读入内存。如果你读入每一行,解析该行,然后将该行写入各种输出文件,你不会占用大量内存。有一个缓存,但我相信在将\n
写入文件后它会被清空。
诀窍是打开输入文件,然后在第一行读取。您想要创建某种字段映射结构,这样您就可以确定要写入每个输出文件的字段。
我会列出您需要写入的所有文件。这样,您可以浏览每个文件的列表。列表中的每个项目都应包含写入该文件所需的信息。
首先,您需要一个文件句柄,因此您知道要写入哪个文件。其次,您需要一个您必须写入该特定输出文件的字段编号列表。
我看到这样的处理循环:
while (my $line = <$input_fh>) { #Line from the input file.
chomp $line;
my @input_line_array = split /\t/, $line;
my $fileHandle;
foreach my $output_file (@outputFileList) { #List of output files.
$fileHandle = $output_file->{FILE_HANDLE};
my @fieldsToWrite;
foreach my $fieldNumber (@{$output_file->{FIELD_LIST}}) {
push $fieldsToWrite, $input_line_array[$field];
}
say $file_handle join "\t", @fieldsToWrite;
}
}
我正在将输入文件的一行读入$line
并将其划分为我放在@input_line_array
中的字段。现在我有了这条线,我必须找出哪些字段写入每个输出文件。
我有一个名为@outputFileList
的列表,它是我要写入的所有输出文件的列表。 $outputFileList[$fileNumber]->{FILE_HANDLE}
包含输出文件$fileNumber
的文件句柄。 $ouputFileList[$fileNumber]->{FIELD_LIST}
是我要写入输出文件$fileNumber
的字段列表。这是@input_line_array
中的字段的索引。所以,如果
$outputFileList[$fileNumber]->{FIELD_LIST} = [0, 1, 2, 4, 6, 8];
表示我想将以下字段写入输出文件:$input_line_array[0]
,$input_line_array[1]
,$input_line_array[2]
,$input_line_array[4]
,$input_line_array[6]
和{{ 1}}按顺序将输出文件$input_line_array[8]
作为制表符分隔列表。
我希望这是有道理的。
最初的问题是在$outputFileList->[$fileNumber]->{FILE_HANDLE}
的第一行读取并将其解析为所需的复杂结构。但是,现在你已经了解了如何存储这个结构,解析第一行不应该是一个太大的问题。
虽然在这个例子中我没有使用面向对象的代码(我把这些东西从我的...中拉出来...我的意思是...在我写这篇文章时的大脑)。我肯定会使用面向对象的代码方法。通过消除错误,它实际上会使事情变得更快。
答案 3 :(得分:0)
这是一个打印第一个样本的单行,你可以写一个shell脚本将不同样本的数据写入不同的文件
perl -F, -lane 'print "@F[0..1] @F[2..9]"' <INPUT_FILE_NAME>