Sortie d'une partie de chaque ligne dans un fichier séparé

14

J'ai un fichier comme celui-ci:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Je veux créer un fichier a.seqcontenant une séquence AGTACTTCCAGGAACGGTGCACTCTCC. b.seqContient de même ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. En bref, Colonne1 doit être utilisé comme nom de fichier de sortie avec extension .seq, puis il doit contenir la séquence colonne2 correspondante. Je peux le faire en écrivant un script perl mais tout ce qui est en ligne de commande sera utile. J'espère entendre bientôt.

user3138373
la source

Réponses:

16

Ma réponse instantanée aurait été, awkmais si vous traitez beaucoup de lignes - et je parle de millions - vous verrez probablement un réel avantage à passer à un «vrai» langage de programmation.

Dans cet esprit (et awkdéjà pris comme réponse), j'ai écrit quelques implémentations dans différents langages et les ai comparées sur le même ensemble de données de 10 000 lignes sur 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

En un coup d'œil, le C est le plus beau, mais c'était un cochon pour courir aussi vite. Pypy et C ++ sont beaucoup plus faciles à écrire et à exécuter suffisamment bien, sauf si vous parlez de plusieurs milliards de lignes. Si tel était le cas, une mise à niveau pour tout faire en RAM ou sur un SSD pourrait être un meilleur investissement qu'une amélioration de code.

Évidemment, pendant le temps que j'ai passé à les parcourir, vous auriez probablement pu traiter quelques centaines de millions d'enregistrements dans l'option la plus lente . Si vous ne pouvez écrire que des awkboucles Bash, faites-le et poursuivez votre vie. J'ai clairement eu trop de temps libre aujourd'hui.

J'ai également testé quelques options multi-thread (en C ++ et Python et hybrides avec GNU parallel) mais la surcharge des threads l'emporte complètement sur tout avantage pour une opération aussi simple (division de chaîne, écriture).

Perl

awk( gawkici) serait honnêtement ma première escale pour tester des données comme celle-ci, mais vous pouvez faire des choses assez similaires en Perl. Syntaxe similaire mais avec une poignée d'écriture légèrement meilleure.

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

Python

J'aime Python. C'est ma langue de travail de jour et c'est juste une langue agréable, solide et incroyablement lisible. Même un débutant pourrait probablement deviner ce qui se passe ici.

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

Vous devez vous rappeler que le pythonbinaire de votre distribution n'est pas la seule implémentation de Python. Lorsque j'ai exécuté ce même test via Pypy, il était plus rapide que C sans aucune optimisation logique supplémentaire. Gardez cela à l'esprit avant d'écrire Python comme un "langage lent".

C

J'ai commencé cet exemple pour voir ce que nous pourrions vraiment faire pour mon processeur, mais franchement, C est un cauchemar à coder si vous ne l'avez pas touché depuis longtemps. Cela a l'inconvénient supplémentaire d'être limité aux lignes de 100 caractères, bien qu'il soit très simple de l'étendre, je n'en avais tout simplement pas besoin.

Ma version originale était plus lente que C ++ et pypy mais après avoir blogué à ce sujet, j'ai obtenu de l' aide de Julian Klode . Cette version est maintenant la plus rapide en raison de ses tampons IO modifiés. C'est aussi beaucoup plus long et plus complexe qu'autre chose.

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

Fonctionne bien et est beaucoup plus facile à écrire que le vrai C. Vous avez toutes sortes de choses qui vous tiennent par la main (surtout en ce qui concerne les cordes et les entrées). Tout cela signifie que vous pouvez réellement simplifier la logique. strtoken C est un porc car il traite la chaîne entière et ensuite nous devons faire toute cette allocation de mémoire fastidieuse. Cela flotte juste le long de la ligne jusqu'à ce qu'il touche l'onglet et nous retirons les segments lorsque nous en avons besoin.

#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

(Pas la version moreutils). C'est une belle syntaxe concise mais OMGSLOW. Je l'utilise peut-être mal.

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

Générateur de faisceaux d'essai

Voici mon générateur de données pour 100 000 lignes de [ATGC] * 64. Ce n'est pas rapide et les améliorations sont les bienvenues.

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
Oli
la source
2
Je dois souligner que l'énumération de toutes vos options de performance peut être aussi gaspilleuse que le simple fait de penser à la première chose qui vous vient à l'esprit. awkest toujours une bonne réponse pour rien de moins que des dizaines de millions. Même si vous augmentez [linéairement] cela jusqu'à un milliard de lignes, C ne vous fait économiser que 1,5 heure sur Perl et 3,6 heures sur awk.
Oli
Maintenant que ma version C ++ est activée, il est tellement plus rapide, je considérerais peut-être C ++ pour un traitement de texte plus simple d'énormes ensembles de données. C'est presque deux fois plus rapide et c'est une différence de plusieurs heures lorsque vous atteignez des milliards de lignes.
Oli
7
xkcd.com/1445
mr.spuratic
1
Je pense que la vitesse de génération de votre faisceau de test est liée au générateur de nombres aléatoires. Vous pouvez le faire plus rapidement en utilisant chaque numéro donne ou générer une distribution homogène, par exemple: 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

Implémentation pure shell:

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

En utilisant awk:

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

À partir du nominé file, imprimez le deuxième champ de chaque enregistrement ( $2) dans un fichier nommé d'après le premier champ ( $1) avec en .seqannexe au nom.

Comme Thor le souligne dans les commentaires, pour un grand ensemble de données, vous pouvez épuiser les descripteurs de fichier, il serait donc sage de fermer chaque fichier après l'écriture :

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file
jasonwryan
la source
Salut ça marche Merci beaucoup .. Pouvez-vous expliquer un peu le code?
user3138373
@ user3138373 J'espère que ça aide ...
jasonwryan
Cela aide .. Merci Pourquoi l'impression ne fonctionnera pas au lieu de printf ??
user3138373
3
S'il y a beaucoup de lignes, tous les descripteurs de fichiers disponibles seront utilisés, vous devriez donc probablement ajouter un close($1".seq").
Thor
1
@Thor, c'est vrai. Cependant, certaines awkimplémentations comme GNU savent comment contourner cela.
Stéphane Chazelas
3

Voici une façon de le faire avec GNU sed:

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

Ou plus efficacement, comme l'a suggéré glenn jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh
Thor
la source
1
Bien que ce soit cool, c'est assez inefficace, devoir générer une commande externe pour chaque ligne. Ce serait un peu mieux d'avoir la sortie sed de toutes les commandes brutes, et de diriger la sortie dans "sh"
glenn jackman
1
@glennjackman: Ce n'était qu'une façon alternative intéressante de le faire. Si l'entrée est grande, awkc'est probablement l'outil le plus efficace à utiliser. Vous avez bien sûr raison de ne pas frayer shpour chaque ligne, j'ai ajouté l'option pipe comme alternative.
Thor