将每行的一部分输出到单独的文件中


14

我有一个像这样的文件:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

我想制作a.seq包含sequence的文件AGTACTTCCAGGAACGGTGCACTCTCC。同样b.seq包含ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT。简而言之,应将Column1用作扩展名的输出文件名,.seq然后在其中具有相应的column2序列。我可以通过编写一个Perl脚本来做到这一点,但是命令行上的任何操作都会有所帮助。希望尽快听到。

Answers:


16

我的反应awk很迅速,但是如果您要处理很多行(我说的是数百万行),那么您可能会发现切换到“真正的”编程语言会带来真正的好处。

考虑到这一点(并且awk已经被回答),我用不同的语言编写了一些实现,并在PCI-E SSD的同一10,000行数据集上对它们进行了基准测试。

me* (C)                0m1.734s
me (C++)               0m1.991s
me (Python/Pypy)       0m2.390s
me (perl)              0m3.024s
Thor+Glenn (sed|sh)    0m3.353s
me (python)            0m3.359s
jasonwryan+Thor (awk)  0m3.779s
rush (while read)      0m6.011s
Thor (sed)             1m30.947s
me (parallel)          4m9.429s

乍一看,C看上去最棒,但是跑得这么快真是猪。除非您要谈论数十亿行否则Pypy和C ++会更容易编写和具有足够好的性能。如果真是这样,那么升级到RAM或SSD上进行全部操作可能比代码改进更好。

显然,在我花费了这些时间之后,您可能会以最慢的速度处理几亿条记录。如果您只能编写awk循环或Bash循环,请这样做并继续生活。今天我显然有太多的业余时间。

我也测试了一些多线程选项(在C ++和Python以及与GNU的混合版本中parallel),但是线程的开销完全超过了这种简单操作(字符串拆分,编写)的任何好处。

佩尔

awkgawk这里)老实说是我这样测试数据的第一站,但是您可以在Perl中做相当类似的事情。语法相似,但编写手法稍好。

perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile

蟒蛇

喜欢 Python。这是我的日常工作语言,只是一种很好的,扎实的和难以置信的可读性语言。即使是初学者,也可能会猜测这里发生了什么。

with open("infile", "r") as f:
    for line in f:
        id, chunk = line.split()
        with open(id + ".seq", "w") as fw:
            fw.write(chunk)

您必须记住,发行版的python二进制文件并不是唯一的Python实现。当我通过Pypy运行相同的测试时,它比C更快并且没有任何进一步的逻辑优化。在以“慢速语言”编写Python之前,请记住这一点。

C

我开始这个例子来看看我们真正可以让我的CPU做些什么,但是坦率地说,如果您很久没有接触C了,那么它就是一场噩梦。尽管扩展起来很简单,但我只是不需要它,这有一个局限性,即限制为100个字符行。

我的原始版本比C ++和pypy慢,但是写完博客后,我得到了朱利安·克洛德(Julian Klode)的帮助。由于调整了IO缓冲区,因此该版本现在是最快的。这也是一个很大更长,更参与比什么都重要。

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

#define BUFLEN (8 * 1024)

int main(void) {
    FILE *fp;
    FILE *fpout;

    char line[100];
    char *id;
    char *token;
    char *buf = malloc(BUFLEN);

    fp = fopen("infile", "r");

    setvbuf ( fp , buf , _IOLBF, BUFLEN );
    while (fgets(line, 100, fp) != NULL) {
        id = strtok(line, "\t");
        token = strtok(NULL, "\t");

        char *fnout = malloc(strlen(id)+5);
        fnout = strcat(fnout, id);
        fnout = strcat(fnout, ".seq");

        fpout = fopen(fnout, "w");
        setvbuf ( fpout , NULL , _IONBF , 0 );
        fprintf(fpout, "%s", token);
        fclose(fpout);
    }
    fclose(fp);

    return 0;
}

C ++

表现很好,而且比实际C.你有各种各样的那个牵着你的手(尤其是当它涉及到字符串和输入)写的东西更容易。所有这些意味着您实际上可以简化逻辑。strtokC语言中的代码是猪,因为它处理了整个字符串,然后我们需要进行所有繁琐的内存分配。这只是沿着线飞来飞去,直到碰到标签,然后根据需要将其拉出。

#include <fstream>
#include <string>
using namespace std;

int main(void) {
    ifstream in("infile");
    ofstream out;
    string line;

    while(getline(in, line)) {
        string::size_type tab = line.find('\t', 0);
        string filename = line.substr(0, tab) + ".seq";
        out.open(filename.c_str());
        out << line.substr(tab + 1);
        out.close();
    }

    in.close();
}

GNU并行

(不是moreutils版本)。这是一个很好的简洁语法,但是是OMGSLOW。我可能用错了。

parallel --colsep '\t' echo {2} \> {1}.seq <infile

测试线束发生器

这是我用于100000行[ATGC] * 64的数据生成器。这不是很快,我们非常欢迎改进。

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile

2
我应该指出,列举所有性能选择可能就像浪费脑海中的第一件事一样浪费。awk对于数千万以下的内容,它仍然是一个很好的答案。即使您将其线性扩展至十亿行,C仍仅比Perl节省1.5个小时,比awk节省3.6个小时。
奥利2014年

现在我的C ++版本是有这么快很多,也许我会考虑的C ++的庞大的数据集更简单的文本处理。它的速度几乎快一倍,而到达数十亿行的时间却相差很多小时。
奥利2014年



1
我认为测试工具的生成速度受随机数生成器的约束。您可以通过使用它给出的每个数字或生成均匀分布来加快速度,例如:paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
2015年

13

纯壳实现:

while read -r filename content ; do
    printf '%s\n' "$content" >> "${filename}.seq"
done < /source/file

12

使用awk

awk '{printf "%s\n", $2>$1".seq"}' file

从提名的file,将每个记录($2)的第二个字段打印到以第一个字段($1)命名的文件名,并.seq附加名称。

正如Thor在评论中指出的那样,对于大型数据集,您可能会用尽文件描述符,因此在写入后关闭每个文件是明智的:

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file

嗨,这很有效,谢谢。您能解释一下代码吗?
user3138373

@ user3138373希望对您有帮助...
jasonwryan 2014年

它有帮助。.谢谢为什么不打印而不是printf?
user3138373

3
如果有很多行,将使用所有可用的文件描述符,因此您可能应该添加一个close($1".seq")
2014年

1
@Thor,是的。不过,某些awk实现(例如GNU)知道如何解决该问题。
斯特凡Chazelas

3

这是使用GNU sed的一种方法:

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:e; d'

或更有效,如glenn jackman所建议:

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh

1
虽然很酷,但是效率很低,必须为每行生成一个外部命令。sed输出所有原始命令,并将输出通过管道
传递

1
@glennjackman:这只是一种有趣的替代方法。如果输入很大,awk则可能是最有效的工具。您当然不对sh每一行都产卵,我已经添加了pipe-option作为替代。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.