Salida de parte de cada línea a un archivo separado

14

Tengo un archivo como este:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Quiero hacer un archivo a.seqque contenga secuencia AGTACTTCCAGGAACGGTGCACTCTCC. Del mismo modo b.seqcontiene ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. En resumen, Column1 debe usarse como nombre de archivo de salida con extensión .seqy luego debe tener la secuencia correspondiente de column2. Puedo hacer esto escribiendo un script en perl, pero cualquier cosa en la línea de comandos será útil. Espero escuchar pronto.

usuario3138373
fuente

Respuestas:

16

Mi respuesta rápida habría sido, awkpero si está procesando muchas líneas, y estoy hablando de millones, probablemente verá un beneficio real al cambiar a un lenguaje de programación "real".

Con eso en mente (y awkya lo estoy tomando como respuesta), escribí algunas implementaciones en diferentes idiomas y las comparé en el mismo conjunto de datos de 10,000 líneas en un 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 un vistazo, la C se ve mejor, pero fue un cerdo correr tan rápido. Pypy y C ++ son mucho más fáciles de escribir y funcionan lo suficientemente bien a menos que esté hablando de miles de millones de líneas. Si ese fuera el caso, una actualización para hacer todo esto en RAM o en un SSD podría ser una mejor inversión que una mejora de código.

Obviamente, en el tiempo que pasé revisando estos, probablemente podría haber procesado unos cientos de millones de registros en la opción más lenta . Si solo puede escribir awko Bash loops, hágalo y continúe con la vida. Claramente tuve mucho tiempo libre hoy.

También probé algunas opciones de subprocesos múltiples (en C ++ y Python e híbridos con GNU parallel) pero la sobrecarga de los subprocesos supera por completo cualquier beneficio para una operación tan simple (división de cadenas, escritura).

Perl

awk( gawkaquí) honestamente sería mi primer puerto de escala para probar datos como este, pero puede hacer cosas bastante similares en Perl. Sintaxis similar pero con un controlador de escritura ligeramente mejor

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

Pitón

Me gusta Python Es mi idioma de trabajo diario y es un lenguaje agradable, sólido e increíblemente legible. Incluso un principiante probablemente podría adivinar lo que está sucediendo aquí.

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

Debe recordar que el pythonbinario de su distribución no es la única implementación de Python que existe. Cuando ejecuté esta misma prueba a través de Pypy, fue más rápido que C sin ninguna otra optimización lógica. Tenga esto en cuenta antes de escribir Python como un "lenguaje lento".

C

Comencé este ejemplo para ver lo que realmente podríamos hacer que mi CPU haga, pero, francamente, C es una pesadilla para codificar si no lo has tocado en mucho tiempo. Esto tiene el inconveniente adicional de estar limitado a líneas de 100 caracteres, aunque es muy simple expandir eso, simplemente no lo necesitaba.

Mi versión original era más lenta que C ++ y pypy, pero después de bloguear sobre ella , recibí ayuda de Julian Klode . Esta versión es ahora la más rápida debido a sus buffers IO ajustados. También es mucho más largo y más complicado que cualquier otra cosa.

#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 desempeña bien y es mucho más fácil de escribir que el C. real. Tiene todo tipo de cosas que sostienen su mano (especialmente cuando se trata de cadenas y entrada). Todo eso significa que puedes simplificar la lógica. strtoken C es un cerdo porque procesa toda la cadena y luego tenemos que hacer toda esa asignación de memoria agotadora. Esto solo se mueve a lo largo de la línea hasta que toca la pestaña y sacamos los segmentos cuando los necesitamos.

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

(No es la versión moreutils). Es una buena sintaxis concisa pero OMGSLOW. Podría estar usándolo mal.

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

Generador de arnés de prueba

Aquí está mi generador de datos para 100000 líneas de [ATGC] * 64. No es rápido y las mejoras son bienvenidas.

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
Oli
fuente
2
Debo señalar que enumerar todas sus opciones de rendimiento puede ser tan derrochador como simplemente ir con lo primero que se me ocurre. awksigue siendo una buena respuesta para menos de decenas de millones. Incluso si escala [linealmente] esto hasta mil millones de líneas, C solo le ahorrará 1.5 horas sobre Perl y 3.6 horas sobre awk.
Oli
Ahora mi C ++ versión aparece en ella es por lo tanto más rápido, tal vez lo consideraría C ++ para el procesamiento de texto más sencilla de grandes conjuntos de datos. Es casi el doble de rápido y eso supone muchas horas de diferencia cuando llegas a miles de millones de líneas.
Oli
77
xkcd.com/1445
mr.spuratic
1
Creo que la velocidad de generación de su arnés de prueba está limitada por el generador de números aleatorios. Puede que sea más rápido mediante el uso de todos los números que da o generar una distribución homogénea, por ejemplo: 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

Implementación de shell puro:

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

Utilizando awk:

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

Desde el nominado file, imprima el segundo campo en cada registro ( $2) en un archivo con el nombre del primer campo ( $1) junto .seqcon el nombre.

Como Thor señala en los comentarios, para un gran conjunto de datos, puede agotar los descriptores de archivo, por lo que sería aconsejable cerrar cada archivo después de escribir :

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file
jasonwryan
fuente
Hola, esto funciona. Muchas gracias. ¿Puedes explicarme un poco el código?
user3138373
@ user3138373 Espero que ayude ...
jasonwryan
Ayuda ... Gracias ¿Por qué no imprimir trabajo en lugar de printf?
user3138373
3
Si hay muchas líneas, se utilizarán todos los descriptores de archivo disponibles, por lo que probablemente debería agregar un close($1".seq").
Thor
1
@Thor, cierto. Sin awkembargo, algunas implementaciones como GNU saben cómo solucionarlo.
Stéphane Chazelas
3

Aquí hay una manera de hacerlo con GNU sed:

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

O más eficientemente, como lo sugiere Glenn Jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh
Thor
fuente
1
Si bien eso es genial, es bastante ineficiente, tener que generar un comando externo para cada línea. Sería un poco mejor tener la salida sed todos los comandos en bruto, y canalizar la salida en "sh"
Glenn Jackman
1
@glennjackman: Esta fue solo una forma alternativa interesante de hacerlo. Si la entrada es grande, awkes probablemente la herramienta más eficiente para usar. Por supuesto, tiene razón sobre no generar shcada línea, he agregado la opción de tubería como alternativa.
Thor