Wyprowadza część każdego wiersza do osobnego pliku

14

Mam taki plik:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Chcę utworzyć plik a.seqzawierający sekwencję AGTACTTCCAGGAACGGTGCACTCTCC. Podobnie b.seqzawiera ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. W skrócie, Kolumna1 powinna być używana jako nazwa pliku wyjściowego z rozszerzeniem, .seqa następnie powinna mieć odpowiednią sekwencję kolumna2. Mogę to zrobić, pisząc skrypt Perla, ale wszystko w wierszu poleceń będzie pomocne. Mam nadzieję usłyszeć wkrótce.

użytkownik3138373
źródło

Odpowiedzi:

16

Moja odpowiedź byłaby szybka, awkale jeśli przetwarzasz wiele linii - a mówię o milionach - prawdopodobnie zobaczysz prawdziwą korzyść z przejścia na „prawdziwy” język programowania.

Mając to na uwadze (i awkjuż jako odpowiedź) napisałem kilka implementacji w różnych językach i porównałem je w tym samym zestawie danych o pojemności 10 000 linii na dysku 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

Na pierwszy rzut oka C wygląda najlepiej, ale to była świnia, żeby biegać tak szybko. Pypy i C ++ są znacznie łatwiejsze do napisania i działają wystarczająco dobrze, chyba że mówimy o wielu miliardach linii. W takim przypadku uaktualnienie do robienia tego wszystkiego w pamięci RAM lub na dysku SSD może być lepszą inwestycją niż ulepszenie kodu.

Oczywiście w czasie, który spędziłem na ich przeglądaniu, prawdopodobnie mógłbyś przetworzyć kilkaset milionów rekordów w najwolniejszej opcji . Jeśli możesz pisać awklub zapętlać Bash, rób to i żyj dalej. Najwyraźniej dzisiaj miałem za dużo wolnego czasu.

Testowałem także kilka opcji wielowątkowych (w C ++ i Python oraz hybryd z GNU parallel), ale narzuty wątków całkowicie przewyższają wszelkie korzyści z tak prostej operacji (dzielenie ciągów, pisanie).

Perl

awk( gawktutaj) byłbym szczerze moim pierwszym portem do testowania takich danych, ale w Perlu możesz robić dość podobne rzeczy. Podobna składnia, ale z nieco lepszym uchwytem do pisania.

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

Pyton

I jak Python. To mój język pracy i po prostu ładny, solidny i niesamowicie czytelny język. Nawet początkujący prawdopodobnie mógłby zgadnąć, co się tutaj dzieje.

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

Musisz pamiętać, że pythonplik binarny twojej dystrybucji nie jest jedyną implementacją Pythona. Kiedy przeprowadziłem ten sam test przez Pypy, było ono szybsze niż C bez dalszej optymalizacji logiki. Pamiętaj o tym, zanim wypisz Python jako „wolny język”.

do

Zacząłem ten przykład, aby zobaczyć, co naprawdę możemy zrobić z moim procesorem, ale szczerze mówiąc, C jest koszmarem do kodowania, jeśli nie dotknąłeś go od dawna. Ma to tę dodatkową wadę, że ogranicza się do linii o długości 100 znaków, chociaż bardzo łatwo ją rozwinąć, po prostu jej nie potrzebowałem.

Moja oryginalna wersja była wolniejsza niż C ++ i pypy, ale po blogowaniu na ten temat otrzymałem pomoc od Juliana Klode . Ta wersja jest teraz najszybsza ze względu na ulepszone bufory IO. Jest także znacznie dłuższy i bardziej zaangażowany niż cokolwiek innego.

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

Działa dobrze i jest znacznie łatwiejszy do pisania niż prawdziwy C. Masz wiele rzeczy, które trzymają cię za rękę (szczególnie jeśli chodzi o napisy i dane wejściowe). Wszystko to oznacza, że ​​możesz uprościć logikę. strtokw C jest wieprzem, ponieważ przetwarza cały ciąg, a następnie musimy wykonać całą tę męczącą alokację pamięci. Po prostu przesuwa się wzdłuż linii, aż dotknie zakładki, i wyciągamy segmenty, gdy są potrzebne.

#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

(Nie wersja Moreutils). To miła, zwięzła składnia, ale OMGSLOW. Mogę źle go używać.

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

Test generatora wiązki przewodów

Oto mój generator danych dla 100000 linii [ATGC] * 64. To nie jest szybkie i ulepszenia są bardzo mile widziane.

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
Oli
źródło
2
Powinienem zwrócić uwagę, że wyliczenie wszystkich opcji wydajności może być tak samo marnotrawstwem, jak samo zrobienie pierwszej rzeczy, jaka przychodzi mi do głowy. awkwciąż jest dobrą odpowiedzią na cokolwiek mniejszego niż dziesiątki milionów. Nawet jeśli [liniowo] skalujesz to do miliarda linii, C oszczędza ci tylko 1,5 godziny w Perlu i 3,6 godziny w awk.
Oli,
Teraz, gdy moja wersja C ++ jest włączona, jest o wiele szybsza, może rozważę C ++ do prostszego przetwarzania tekstu w dużych zestawach danych. To prawie dwa razy szybciej i to wiele różnica godziny, kiedy dojdziesz do miliardów wierszy.
Oli,
7
xkcd.com/1445
mr.spuratic
1
Myślę, że prędkość generowania testowej wiązki jest ograniczona przez generator liczb losowych. Możesz przyspieszyć, używając każdej podanej liczby lub wygenerować jednorodny rozkład, np 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

Implementacja czystej powłoki:

while read -r filename content ; do
    printf '%s\n' "$content" >> "${filename}.seq"
done < /source/file
wysypka
źródło
12

Używanie awk:

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

Z nominowanego filewydrukuj drugie pole w każdym rekordzie ( $2) do pliku nazwanego po pierwszym polu ( $1) z .seqdopiskiem do nazwy.

Jak wskazuje Thor w komentarzach, w przypadku dużego zestawu danych możesz wyczerpać deskryptory plików, więc dobrze byłoby zamknąć każdy plik po napisaniu :

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file
jasonwryan
źródło
Cześć To działa Bardzo dziękuję .. Czy możesz trochę wyjaśnić kod?
user3138373,
@ user3138373 Mam nadzieję, że to pomaga ...
jasonwryan
Pomaga .. Dzięki Dlaczego druk nie działa zamiast printf?
user3138373,
3
Jeśli jest wiele wierszy, użyte zostaną wszystkie dostępne deskryptory plików, więc prawdopodobnie powinieneś dodać close($1".seq").
Thor
1
@Thor, prawda. awkJednak niektóre implementacje, takie jak GNU, potrafią to obejść.
Stéphane Chazelas
3

Oto jeden ze sposobów, w jaki możesz to zrobić za pomocą GNU sed:

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

Lub wydajniej, jak sugeruje glenn jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh
Thor
źródło
1
Chociaż jest to fajne, jest dość nieefektywne, ponieważ trzeba spawnować zewnętrzne polecenie dla każdej linii. Byłoby trochę lepiej, gdyby sed
wypisał
1
@glennjackman: To był tylko interesujący alternatywny sposób na zrobienie tego. Jeśli dane wejściowe są duże, awkjest to prawdopodobnie najbardziej wydajne narzędzie do użycia. Oczywiście masz rację, że nie spawnujesz się shdla każdej linii, dodałem opcję potoku jako alternatywę.
Thor