Виведіть частину кожного рядка в окремий файл


14

У мене такий файл:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Я хочу зробити файл, a.seqякий містить послідовність AGTACTTCCAGGAACGGTGCACTCTCC. Аналогічно b.seqмістить ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. Коротше кажучи, Column1 має використовуватися як ім'я вихідного файла з розширенням, .seqі тоді він повинен мати відповідну послідовність column2 у ньому. Я можу це зробити, написавши сценарій perl, але будь-що в командному рядку буде корисним. Сподіваюся, що скоро почуємо.

Відповіді:


16

Моя швидка відповідь була б, awkале якщо ви обробляєте багато рядків - а я кажу про мільйони - ви, швидше за все, побачите реальну користь від переходу на "справжню" мову програмування.

Зважаючи на це (і awkвже прийнятий як відповідь), я написав декілька реалізацій на різних мовах і порівняв їх на одному 10000-рядковому наборі даних на PCI-E SSD.

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

З першого погляду C виглядає найкраще, але бігти так швидко, як свиня. Pypy та C ++ набагато простіше писати та виконувати досить добре, якщо ви не говорите про багато мільярдів рядків. Якби це було так, оновлення виконання цього все в оперативній пам'яті або на SSD може бути кращою інвестицією, ніж поліпшення коду.

Очевидно, що за час, який я витратив на перегляд цих питань, ви, ймовірно, могли обробити кілька сотень мільйонів записів найповільнішим варіантом . Якщо ви можете лише писати awkабо баш-петлі, зробіть це і продовжуйте життя. Я, очевидно, мав занадто багато вільного часу сьогодні.

Я також протестував деякі багатопотокові варіанти (у C ++ та Python та гібридах з GNU parallel), але накладні потоки повністю переважують будь-яку користь для такої простої операції (розбиття рядків, запис).

Perl

awk( gawkтут), чесно, був би моїм першим портом виклику для тестування таких даних, але ви можете зробити досить схожі речі в Perl. Схожий синтаксис, але з дещо кращою ручкою запису.

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

Пітон

Мені подобається Python. Це моя щоденна мова роботи, і це просто приємна, міцна і неймовірно читаема мова. Навіть новачок міг би здогадатися, що тут відбувається.

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

Ви повинні пам’ятати, що pythonбінарний файл вашого дистрибутива - не єдина реалізація Python там. Коли я провів цей самий тест через Pypy, він виявився швидшим, ніж C, без подальшої логічної оптимізації. Майте це на увазі, перш ніж писати Python як "повільну мову".

С

Я почав цей приклад, щоб побачити, що насправді може зробити мій процесор, але, чесно кажучи, C - це кошмар, який потрібно кодувати, якщо ви його довго не торкалися. Це має додатковий мінус обмеження на 100-лінійних ліній, хоча розширити це дуже просто, мені це просто не потрібно.

Моя оригінальна версія була повільніше, ніж C ++ та pypy, але після ведення блогів про неї я отримав деяку допомогу від Джуліана Клоде . Ця версія зараз найшвидша через налаштовані буфери IO. Це також набагато довше і більше, ніж все інше.

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

Виступає добре і писати набагато простіше, ніж справжній C. У вас є всілякі речі, які тримають вашу руку (особливо якщо мова йде про струни та введення). Все це означає, що ви можете насправді спростити логіку. strtokу C - свиня, оскільки вона обробляє всю струну, і тоді нам потрібно зробити все, що стомлює розподіл пам'яті. Це просто прошивається по лінії, поки вона не потрапить на вкладку, і ми витягнемо сегменти, як нам потрібно.

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

(Не версія moreutils). Це приємний стислий синтаксис, але OMGSLOW. Можливо, я неправильно його використовую.

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

Випробувальний генератор джгутів

Ось мій генератор даних для 100000 рядків [ATGC] * 64. Це не швидко, і покращення дуже вітаються.

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

2
Я мушу зазначити, що перерахування всіх ваших варіантів продуктивності може бути настільки марно, як і просто перше, що спадає на думку. awkвсе ще є чудовою відповіддю на що-небудь менше, ніж десятки мільйонів. Навіть якщо ви [лінійно] масштабуєте це до мільярда рядків, C заощаджує вас лише 1,5 години на Perl і 3,6 години на awk.
Олі

Тепер мій C ++ версії на є так набагато швидше, може бути , я б розглянути C ++ для більш простої обробки тексту величезних наборів даних. Це майже вдвічі швидше, і це багато годинна різниця, коли ви добираєтеся до мільярдів ліній.
Олі



1
Я думаю, що швидкість генерації вашого тестового джгута пов'язана генератором випадкових чисел. Ви можете зробити це швидше, використовуючи кожен номер він дає або генерувати рівномірний розподіл, наприклад: paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile.
Тор

13

Чиста реалізація оболонки:

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

12

Використання awk:

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

З номінованого fileдрукуйте друге поле кожного запису ( $2) у файл, названий після першого поля ( $1) з .seqдоданим до імені.

Як вказує Тор у коментарях, для великого набору даних ви можете вичерпати дескриптори файлів, тому було б розумно закрити кожен файл після написання :

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

Привіт Це працює Дякую. .. Чи можете ви трохи пояснити код?
користувач3138373

@ user3138373 Сподіваюся, що це допомагає ...
jasonwryan

Це допомагає .. Дякую Чому друкована робота не замість printf ??
користувач3138373

3
Якщо рядків багато, будуть використовуватися всі доступні дескриптори файлів, тому, ймовірно, слід додати close($1".seq").
Тор

1
@Тор, правда. Деякі awkреалізації, як-от GNU, знають, як обійти це.
Стефан Хазелас

3

Ось один із способів зробити це за допомогою GNU sed:

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

Або більш ефективно, як пропонує глен Джекман :

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

1
Незважаючи на те, що це круто, це досить неефективно, що потрібно породити зовнішню команду для кожного рядка. Було б трохи краще, щоб sed виводив усі необроблені команди і
передав

1
@glennjackman: Це був просто цікавий альтернативний спосіб зробити це. Якщо вхід великий, awkмабуть, це найбільш ефективний інструмент для використання. Ви, звичайно, маєте рацію щодо нерестування shдля кожного рядка, я додав опцію труби як альтернативу.
Тор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.