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

use*_*373 14 sed awk text-processing

我有一个这样的文件:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA
Run Code Online (Sandbox Code Playgroud)

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

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

乍一看,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
Run Code Online (Sandbox Code Playgroud)

Python

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

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

C

我开始这个例子是为了看看我们可以真正让我的 CPU 做什么,但坦率地说,如果你很长时间没有接触 C,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;
}
Run Code Online (Sandbox Code Playgroud)

C++

表现很好,而且比实际C.你有各种各样的那个牵着你的手(尤其是当它涉及到字符串和输入)写的东西更容易。所有这一切都意味着您实际上可以简化逻辑。strtok在 C 中是一个猪,因为它处理整个字符串,然后我们需要完成所有那些令人厌烦的内存分配。这只是沿着线移动,直到它碰到标签,然后我们根据需要将它们拉出。

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

GNU并行

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

parallel --colsep '\t' echo {2} \> {1}.seq <infile
Run Code Online (Sandbox Code Playgroud)

测试线束生成器

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

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

  • http://xkcd.com/1445/ (8认同)
  • 我应该指出,枚举*所有*您的性能选项可能与只考虑想到的第一件事一样浪费。`awk` 仍然是一个很好的答案,对于少于数千万的事物。即使你 [线性地] 将其扩展到 10 亿行,C 只比 Perl 节省 1.5 小时,比 awk 节省 3.6 小时。 (2认同)

jas*_*yan 13

使用awk

awk '{printf "%s\n", $2>$1".seq"}' file
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

rus*_*ush 13

纯shell实现:

while read -r filename content ; do
    printf '%s\n' "$content" >> "${filename}.seq"
done < /source/file
Run Code Online (Sandbox Code Playgroud)