3

我必须处理格式为 10-20GB 的文本文件:field1 field2 field3 field4 field5

我想将 field2 每一行的数据解析为几个文件之一;被推入的文件由 field4 中的值逐行确定。field2 中有 25 个不同的可能值,因此数据可以解析成 25 个不同的文件。

我尝试过使用 Perl(慢)和 awk(更快但仍然很慢) - 有人对替代方法有任何建议或指示吗?

仅供参考,这是我尝试使用的 awk 代码;请注意,我不得不恢复浏览大文件 25 次,因为我无法在 awk 中一次打开 25 个文件:

chromosomes=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25)
for chr in ${chromosomes[@]}
do

awk < my_in_file_here -v pat="$chr" '{if ($4 == pat) for (i = $2; i <= $2+52; i++) print i}' >> my_out_file_"$chr".query 

done
4

6 回答 6

15

使用 Perl,在初始化期间打开文件,然后将每一行的输出匹配到适当的文件:

#! /usr/bin/perl

use warnings;
use strict;

my @values = (1..25);

my %fh;
foreach my $chr (@values) {
  my $path = "my_out_file_$chr.query";
  open my $fh, ">", $path
    or die "$0: open $path: $!";

  $fh{$chr} = $fh;
}

while (<>) {
  chomp;
  my($a,$b,$c,$d,$e) = split " ", $_, 5;

  print { $fh{$d} } "$_\n"
    for $b .. $b+52;
}
于 2009-12-17T03:05:14.043 回答
7

你知道它为什么慢吗?这是因为您正在使用外壳 for 循环处理该大文件 25 次。!!

awk '
$4 <=25 {
    for (i = $2; i <= $2+52; i++){
        print i >> "my_out_file_"$4".query"
    }
}' bigfile
于 2009-12-17T02:05:31.383 回答
7

这是Python中的解决方案。我已经在我制作的一个小假文件上对其进行了测试。我认为即使是大文件,这也是可以接受的快,因为大部分工作将由 Python 内部的 C 代码完成。我认为这是一个令人愉快且易于理解的程序;我更喜欢 Python 而不是 Perl。

import sys

s_usage = """\
Usage: csplit <filename>
Splits input file by columns, writes column 2 to file based on chromosome from column 4."""

if len(sys.argv) != 2 or sys.argv[1] in ("-h", "--help", "/?"):

    sys.stderr.write(s_usage + "\n")
    sys.exit(1)


# replace these with the actual patterns, of course
lst_pat = [
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y'
]


d = {}
for s_pat in lst_pat:
    # build a dictionary mapping each pattern to an open output file
    d[s_pat] = open("my_out_file_" + s_pat, "wt")

if False:
    # if the patterns are unsuitable for filenames (contain '*', '?', etc.) use this:
    for i, s_pat in enumerate(lst_pat):
        # build a dictionary mapping each pattern to an output file
        d[s_pat] = open("my_out_file_" + str(i), "wt")

for line in open(sys.argv[1]):
    # split a line into words, and unpack into variables.
    # use '_' for a variable name to indicate data we don't care about.
    # s_data is the data we want, and s_pat is the pattern controlling the output
    _, s_data, _, s_pat, _ = line.split()
    # use s_pat to get to the file handle of the appropriate output file, and write data.
    d[s_pat].write(s_data + "\n")

# close all the output file handles.
for key in d:
    d[key].close()

编辑:这里有一些关于这个程序的更多信息,因为你似乎会使用它。

所有的错误处理都是隐式的。如果发生错误,Python 将“引发异常”,从而终止处理。例如,如果其中一个文件无法打开,该程序将停止执行,Python 将打印一个回溯,显示是哪一行代码导致了异常。我可以用“try/except”块包裹关键部分来捕获错误,但是对于这么简单的程序,我没有看到任何意义。

这很微妙,但是会检查输入文件的每一行是否正好有五个单词。当此代码解压缩一行时,它会将其分解为五个变量。(变量名“_”是一个合法的变量名,但是 Python 社区有一个约定,可以将它用于您实际上并不关心的变量。)如果在输入行解压成五个变量。如果您的输入文件有时可以在一行中有四个单词,或者六个或更多,您可以修改程序以不引发异常;将主循环更改为:

for line in open(sys.argv[1]):
    lst = line.split()
    d[lst[3]].write(lst[1] + "\n")

这会将行拆分为单词,然后将整个单词列表分配给单个变量 lst。所以那行代码并不关心行中有多少单词。然后下一行索引到列表中以获取值。由于 Python 使用 0 开始索引列表,因此第二个单词是lst[1],第四个单词是lst[3]. 只要列表中至少有四个单词,那行代码也不会引发异常。

当然,如果该行的第四个单词不在文件句柄字典中,Python 也会为此引发异常。那将停止处理。以下是一些示例代码,说明如何使用“try/except”块来处理此问题:

for line in open(sys.argv[1]):
    lst = line.split()
    try:
        d[lst[3]].write(lst[1] + "\n")
    except KeyError:
        sys.stderr.write("Warning: illegal line seen: " + line)

祝你的项目好运。

编辑:@larelogio 指出此代码与 AWK 代码不匹配。AWK 代码有一个我不理解的额外 for 循环。这是执行相同操作的 Python 代码:

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    for i in range(n, n+53):
        d[lst[3]].write(i + "\n")

这是另一种方法。这可能会快一点,但我没有测试过,所以我不确定。

for line in open(sys.argv[1]):
    lst = line.split()
    n = int(lst[1])
    s = "\n".join(str(i) for i in range(n, n+53))
    d[lst[3]].write(s + "\n")

这会构建一个包含所有要写入的数字的字符串,然后将它们写入一个块中。.write()与调用53 次相比,这可能会节省时间。

于 2009-12-21T08:00:08.377 回答
1

有时 awk 不是答案。

有时脚本语言不是答案,当你硬着头皮拖下你的 K&R 副本并破解一些 C 代码时,你会更好。

如果您的操作系统使用并发进程和进程间通信来实现管道,而不是大的临时文件,您可以做的是编写一个重新格式化行的 awk 脚本,将选择器字段放在行的开头使用 scanf() 轻松读取格式,编写一个打开 25 个文件并在其中分配行的 C 程序,并将 awk 脚本输出通过管道传输到 C 程序中。

于 2009-12-19T18:09:26.917 回答
1

听起来你已经在路上了,但我只想提到内存映射 I/O 在处理巨大的文件时有很大的帮助。曾经有一段时间我不得不用 Visual Basic 5 解析一个 .5GB 的二进制文件(是的)……导入CreateFileMappingAPI 让我可以在几分钟内解析文件(并创建几个 gig 的“人类可读”文件) 。并且只用了半个小时左右的时间就实现了。

这是一个描述 Microsoft 平台上 API 的链接,尽管我确信 MMIO 应该在几乎任何平台上:MSDN

祝你好运!

于 2009-12-21T21:11:06.200 回答
0

有一些预先计算可能会有所帮助。

例如,您可以预先计算字段 2 的每个值的输出。承认他们像 field4 一样是 25 岁:

my %tx = map {my $tx=''; for my $tx1 ($_ .. $_+52) {$tx.="$tx1\n"}; $_=>$tx} (1..25);

后面写的时候可以做print {$fh{$pat}} $tx{$base};

于 2009-12-22T14:55:16.273 回答