Saída de parte de cada linha para um arquivo separado


14

Eu tenho um arquivo como este:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Eu quero fazer o arquivo a.seqque contém a sequência AGTACTTCCAGGAACGGTGCACTCTCC. Da mesma forma b.seqcontém ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. Em resumo, a Coluna1 deve ser usada como nome do arquivo de saída com extensão .seqe, em seguida, deve ter a sequência da coluna2 correspondente. Eu posso fazer isso escrevendo um script perl, mas qualquer coisa na linha de comando será útil. Espero ouvir em breve.

Respostas:


16

Minha resposta instantânea teria sido, awkmas se você estiver processando muitas linhas - e eu estou falando de milhões - provavelmente verá um benefício real ao mudar para uma linguagem de programação "real".

Com isso em mente (e awkjá sendo tomado como resposta), escrevi algumas implementações em diferentes idiomas e as comparei no mesmo conjunto de dados de 10.000 linhas em um SSD PCI-E.

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

De relance, o C parece melhor, mas era um porco conseguir correr tão rápido. Pypy e C ++ são muito mais fáceis de escrever e executar com bom desempenho, a menos que você esteja falando de muitos bilhões de linhas. Se esse fosse o caso, uma atualização para fazer tudo isso na RAM ou em um SSD poderia ser um investimento melhor do que uma melhoria no código.

Obviamente, durante o tempo que passei por isso, você provavelmente processou algumas centenas de milhões de registros na opção mais lenta . Se você pode escrever apenas awkloops ou Bash, faça isso e continue com a vida. Eu claramente tinha muito tempo livre hoje.

Também testei algumas opções multithread (em C ++ e Python e híbridos com GNU parallel), mas a sobrecarga de threads supera completamente qualquer benefício para uma operação tão simples (divisão de caracteres, gravação).

Perl

awk( gawkaqui) seria honestamente o meu primeiro porto de escala para testar dados como esse, mas você pode fazer coisas bastante semelhantes no Perl. Sintaxe semelhante, mas com um identificador de escrita um pouco melhor.

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

Pitão

Eu gosto de Python. É a linguagem do meu trabalho diário e é apenas uma linguagem agradável, sólida e incrivelmente legível. Mesmo um iniciante provavelmente poderia adivinhar o que está acontecendo aqui.

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

Você deve se lembrar que o pythonbinário da sua distribuição não é a única implementação do Python disponível. Quando eu executei esse mesmo teste no Pypy, era mais rápido que o C, sem qualquer otimização lógica adicional. Lembre-se disso antes de escrever o Python como uma "linguagem lenta".

C

Comecei este exemplo para ver o que realmente poderíamos fazer minha CPU, mas, francamente, C é um pesadelo para codificar se você não o toca há muito tempo. Isso tem a desvantagem de estar limitado a linhas de 100 caracteres, embora seja muito simples expandir isso, eu simplesmente não precisava disso.

Minha versão original era mais lenta que o C ++ e o pypy, mas depois de escrever sobre o blog, recebi ajuda de Julian Klode . Esta versão é agora a mais rápida devido aos seus buffers de E / S ajustados. Também é muito mais longo e mais envolvido do que qualquer outra coisa.

#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 ++

Tem bom desempenho e é muito mais fácil escrever do que o real C. Você tem todo tipo de coisa que segura sua mão (especialmente quando se trata de strings e entradas). Tudo isso significa que você pode realmente simplificar a lógica. strtokem C é um porco, porque processa toda a cadeia e, em seguida, precisamos fazer toda essa alocação cansativa de memória. Isso apenas passa pela linha até atingir a aba e puxamos os segmentos conforme necessário.

#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 Parallel

(Não é a versão moreutils). É uma sintaxe concisa agradável, mas OMGSLOW. Eu posso estar usando errado.

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

Gerador de equipamento de teste

Aqui está meu gerador de dados para 100000 linhas de [ATGC] * 64. Não é rápido e as melhorias são muito bem-vindas.

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

2
Devo salientar que enumerar todas as suas opções de desempenho pode ser tão inútil quanto seguir a primeira coisa que vem à mente. awkainda é uma boa resposta para algo menos que dezenas de milhões. Mesmo se você escalar linearmente isso em até um bilhão de linhas, C estará economizando apenas 1,5 horas em Perl e 3,6 horas em awk.
Oli

Meu C versão agora ++ é lá é assim muito mais rápido, talvez eu consideraria C ++ para processamento de texto mais simples de grandes conjuntos de dados. É quase o dobro da velocidade e há muitas horas de diferença quando você chega a bilhões de linhas.
Oli



1
Eu acho que a velocidade de geração do seu equipamento de teste é limitada pelo gerador de números aleatórios. Você pode torná-lo mais rápido usando cada número dá ou gerar uma distribuição homogênea, por exemplo: paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile.
Thor

13

Implementação pura do shell:

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

12

Usando awk:

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

No nomeado file, imprima o segundo campo em cada registro ( $2) em um arquivo nomeado após o primeiro campo ( $1) .seqanexado ao nome.

Como Thor aponta nos comentários, para um grande conjunto de dados, você pode esgotar os descritores de arquivo, por isso seria sensato fechar cada arquivo depois de escrever :

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

Oi Isso funciona Muito obrigado .. Você pode explicar um pouco o código?
user3138373

@ user3138373 Espero que ajude ...
jasonwryan

Ajuda .. Obrigado Por que não imprimirá trabalho em vez de printf ??
user3138373

3
Se houver muitas linhas, todos os descritores de arquivos disponíveis serão usados; portanto, você deve adicionar a close($1".seq").
Thor

1
@ Thor, é verdade. awkPorém, algumas implementações como o GNU sabem como solucionar isso.
Stéphane Chazelas

3

Aqui está uma maneira de você fazer isso com o GNU sed:

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

Ou com mais eficiência, conforme sugerido por glenn jackman :

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

1
Enquanto isso é legal, é bastante ineficiente, tendo que gerar um comando externo para cada linha. Seria um pouco melhor com a saída sed todos os comandos crus, e canalizar a saída para "sh"
Glenn Jackman

1
@glennjackman: Esta foi apenas uma maneira alternativa interessante de fazê-lo. Se a entrada for grande, awkprovavelmente é a ferramenta mais eficiente a ser usada. É claro que você está certo em não gerar shpara cada linha, eu adicionei a opção de tubulação como alternativa.
Thor
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.