Saída de cada linha para um arquivo separado

13

Eu tenho um arquivo como este:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

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

    
por user3138373 12.11.2014 / 22:26

4 respostas

17

Minha resposta rápida teria sido awk , mas se você estiver processando muitas linhas - e 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 awk já sendo tomado como uma 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 para correr tão rápido. Pypy e C ++ são muito mais fáceis de escrever e executar bem o suficiente , a menos que você esteja falando sobre muitos bilhões de linhas. Se fosse esse o caso, uma atualização para fazer isso tudo na RAM ou em um SSD poderia ser um investimento melhor do que uma melhoria de código.

Obviamente, no tempo que passei, você provavelmente poderia ter processado algumas centenas de milhões de registros na opção mais lenta . Se você só pode escrever awk ou loops Bash, faça isso e continue com a vida. Eu claramente tinha muito tempo livre hoje.

Eu também testei algumas opções multi-threaded (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 strings, escrita).

Perl

awk ( gawk here) seria honestamente o meu primeiro porto de escala para testar dados como este, mas você pode fazer coisas parecidas em Perl. Sintaxe semelhante, mas com um identificador de escrita ligeiramente melhor.

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

Python

Eu gosto Python. É a minha linguagem de trabalho do dia e é apenas uma linguagem agradável, sólida e incrivelmente legível. Até 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 lembrar que o binário python da sua distribuição não é a única implementação do Python por aí. Quando fiz este mesmo teste no Pypy, foi mais rápido que C sem qualquer otimização lógica adicional. Tenha isso em mente antes de escrever o Python como uma "linguagem lenta".

C

Eu iniciei este exemplo para ver o que poderíamos fazer com a minha CPU, mas, francamente, C é um pesadelo para codificar se você não tocou em um longo tempo. Isso tem a desvantagem de estar limitado a linhas de 100 caracteres, embora seja muito simples expandir isso, eu não precisava disso.

Minha versão original era mais lenta que C ++ e pypy mas depois de blogar sobre isso alguma ajuda de Julian Klode . Esta versão é agora a mais rápida por causa de seus buffers IO ajustados. Também é um 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 ++

Se executa bem e é muito mais fácil de escrever do que o real C. Você tem todo tipo de coisas que seguram sua mão (especialmente quando se trata de strings e entrada). Tudo isso significa que você pode realmente simplificar a lógica. strtok em C é um porco porque processa toda a cadeia e, em seguida, precisamos fazer toda essa alocação de memória cansativa. Isso apenas flerta ao longo da linha até atingir a aba e puxar os segmentos para fora conforme precisarmos deles.

#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 mais recente). É uma sintaxe concisa legal, mas OMGSLOW. Eu posso estar usando errado.

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

Gerador de chicotes de teste

Aqui está o meu gerador de dados para 100.000 linhas de [ATGC] * 64. Não é rápido e 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
    
por 13.11.2014 / 11:51
13

Implementação do shell puro:

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

Usando awk :

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

Do file indicado, imprima o segundo campo em cada registro ( $2 ) em um arquivo com o mesmo nome do primeiro campo ( $1 ) com .seq anexado ao nome.

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

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file
    
por 12.11.2014 / 22:32
3

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

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

Ou mais eficientemente, como sugerido por glenn jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo  > .seq:' | sh
    
por 13.11.2014 / 00:10