Geben Sie einen Teil jeder Zeile in eine separate Datei aus


14

Ich habe eine Datei wie diese:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Ich möchte eine Datei erstellen, a.seqdie eine Sequenz enthält AGTACTTCCAGGAACGGTGCACTCTCC. Ebenso b.seqenthält ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. Kurz gesagt, Spalte1 sollte als Ausgabedateiname mit Erweiterung verwendet werden .seqund dann die entsprechende Spalte2-Sequenz enthalten. Ich kann dies tun, indem ich ein Perl-Skript schreibe, aber alles auf der Kommandozeile ist hilfreich. Ich hoffe, bald zu hören.

Antworten:


16

Meine schnelle Antwort wäre gewesen, awkaber wenn Sie viele Zeilen verarbeiten - und ich spreche von Millionen - werden Sie wahrscheinlich einen echten Vorteil beim Umstieg auf eine "echte" Programmiersprache sehen.

Vor diesem awkHintergrund habe ich einige Implementierungen in verschiedenen Sprachen geschrieben und sie auf einem PCI-E-SSD-Dataset mit 10.000 Zeilen verglichen.

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

Auf den ersten Blick sieht das C am besten aus, aber es war ein Schwein, so schnell zum Laufen zu kommen. Pypy und C ++ sind viel einfacher zu schreiben und arbeiten gut genug, es sei denn, Sie sprechen über viele Milliarden Zeilen. In diesem Fall ist ein Upgrade auf RAM oder SSD möglicherweise eine bessere Investition als eine Code-Verbesserung.

Offensichtlich hätten Sie in der Zeit, die ich damit verbracht habe, diese zu durchlaufen, wahrscheinlich ein paar hundert Millionen Datensätze in der langsamsten Option verarbeitet . Wenn Sie nur awkBash-Loops schreiben oder schreiben können , tun Sie dies und machen Sie mit dem Leben weiter. Ich hatte heute eindeutig zu viel Freizeit.

Ich habe auch einige Multithread-Optionen getestet (in C ++ und Python und Hybrids mit GNU parallel), aber der Overhead von Threads überwiegt den Nutzen einer solch einfachen Operation (Aufteilen von Strings, Schreiben) vollständig.

Perl

awk( gawkhier) wäre ehrlich gesagt meine erste Anlaufstelle, um solche Daten zu testen, aber Sie können in Perl ziemlich ähnliche Dinge tun. Ähnliche Syntax, jedoch mit etwas besserem Schreibgriff.

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

Python

Ich mag Python. Es ist meine Arbeitssprache und es ist einfach eine schöne, solide und unglaublich lesbare Sprache. Sogar ein Anfänger könnte wahrscheinlich erraten, was hier passiert.

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

Sie müssen bedenken, dass die pythonBinärdatei Ihrer Distribution nicht die einzige Implementierung von Python ist. Als ich denselben Test über Pypy durchführte, war er ohne weitere Logikoptimierung schneller als C. Denken Sie daran, bevor Sie Python als "langsame Sprache" abschreiben.

C

Ich habe dieses Beispiel gestartet, um zu sehen, was wir wirklich von meiner CPU erwarten können, aber ehrlich gesagt ist C ein Albtraum, wenn Sie es schon lange nicht mehr angesprochen haben. Dies hat den zusätzlichen Nachteil, dass es auf 100-Zeichen-Zeilen beschränkt ist, obwohl es sehr einfach ist, das zu erweitern, ich brauchte es einfach nicht.

Meine ursprüngliche Version war langsamer als C ++ und pypy, aber nachdem ich darüber gebloggt hatte, bekam ich etwas Hilfe von Julian Klode . Diese Version ist jetzt aufgrund der optimierten E / A-Puffer die schnellste. Es ist auch viel länger und komplizierter als alles andere.

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

Läuft gut und ist viel einfacher zu schreiben als echtes C. Sie haben alle möglichen Dinge, die Sie in der Hand halten (insbesondere, wenn es um Zeichenfolgen und Eingaben geht). All dies bedeutet, dass Sie die Logik tatsächlich vereinfachen können. strtokin C ist ein Schwein, weil es die gesamte Zeichenfolge verarbeitet, und dann müssen wir all diese lästigen Speicherzuweisungen vornehmen. Dies läuft einfach entlang der Linie, bis es auf die Lasche trifft, und wir ziehen die Segmente nach Bedarf heraus.

#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

(Nicht die moreutils Version). Es ist eine schöne kurze Syntax, aber OMGSLOW. Ich könnte es falsch benutzen.

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

Kabelbaumgenerator testen

Hier ist mein Datengenerator für 100000 Zeilen [ATGC] * 64. Es ist nicht schnell und Verbesserungen sind sehr willkommen.

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

2
Ich möchte darauf hinweisen, dass das Aufzählen aller Ihrer Leistungsoptionen so verschwenderisch sein kann, als wenn Sie nur mit dem Ersten arbeiten, was Ihnen in den Sinn kommt. awkist immer noch eine gute Antwort für weniger als zehn Millionen. Selbst wenn Sie dies [linear] auf eine Milliarde Zeilen skalieren, spart Ihnen C nur 1,5 Stunden gegenüber Perl und 3,6 Stunden gegenüber awk.
Oli

Jetzt ist meine C ++ - Version dort so viel schneller, vielleicht würde ich C ++ für eine einfachere Textverarbeitung von riesigen Datenmengen in Betracht ziehen. Es ist fast doppelt so schnell und das sind viele Stunden Unterschied, wenn Sie Milliarden von Zeilen erreichen.
Oli



1
Ich denke, die Generierungsgeschwindigkeit Ihres Testgeschirrs wird durch den Zufallsgenerator bestimmt. Sie können es schneller machen, indem Sie jede gegebene Zahl verwenden oder eine homogene Verteilung erzeugen, z 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

Reine Shell-Implementierung:

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

12

Verwenden von awk:

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

fileDrucken Sie aus den Nominierten das zweite Feld in jedem Datensatz ( $2) in eine Datei, die nach dem ersten Feld ( $1) benannt ist, und .seqhängen Sie es an den Namen an.

Wie Thor in den Kommentaren ausführt , können Sie für einen großen Datensatz die Dateideskriptoren erschöpfen. Es ist daher ratsam, jede Datei nach dem Schreiben zu schließen :

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

Hallo, das funktioniert. Vielen Dank. Kannst du den Code ein bisschen erklären?
user3138373

@ user3138373 Hoffe, dass hilft ...
Jasonwryan

Es hilft .. Danke Warum wird nicht statt printf drucken?
user3138373

3
Wenn es viele Zeilen gibt, werden alle verfügbaren Dateideskriptoren verwendet, daher sollten Sie wahrscheinlich eine hinzufügen close($1".seq").
Thor

1
@Thor, stimmt. Einige awkImplementierungen wie GNUs wissen jedoch, wie man das umgeht.
Stéphane Chazelas

3

Hier ist eine Möglichkeit, wie Sie es mit GNU sed machen können:

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

Oder effizienter, wie von Glenn Jackman vorgeschlagen :

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

1
Das ist zwar cool, aber ineffizient, da für jede Zeile ein externer Befehl erzeugt werden muss. Es wäre ein bisschen besser, wenn das sed alle rohen Befehle ausgibt und die Ausgabe
glenn jackman 13.11.14 weiterleitet

1
@glennjackman: Dies war nur eine interessante Alternative. Wenn die Eingabe groß ist, awkist dies wahrscheinlich das effizienteste Werkzeug. Sie haben natürlich Recht, nicht shfür jede Zeile zu laichen , ich habe die Pipe-Option als Alternative hinzugefügt.
Thor
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.