Meine schnelle Antwort wäre gewesen, awk
aber 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 awk
Hintergrund 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 awk
Bash-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
( gawk
hier) 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 python
Binä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. strtok
in 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
awk
ist 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.paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
.Reine Shell-Implementierung:
quelle
Verwenden von
awk
:file
Drucken Sie aus den Nominierten das zweite Feld in jedem Datensatz ($2
) in eine Datei, die nach dem ersten Feld ($1
) benannt ist, und.seq
hä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 :
quelle
close($1".seq")
.awk
Implementierungen wie GNUs wissen jedoch, wie man das umgeht.Hier ist eine Möglichkeit, wie Sie es mit GNU sed machen können:
Oder effizienter, wie von Glenn Jackman vorgeschlagen :
quelle
awk
ist dies wahrscheinlich das effizienteste Werkzeug. Sie haben natürlich Recht, nichtsh
für jede Zeile zu laichen , ich habe die Pipe-Option als Alternative hinzugefügt.