Вывести часть каждой строки в отдельный файл

14

У меня есть файл, как это:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Я хочу сделать файл, a.seqкоторый содержит последовательность AGTACTTCCAGGAACGGTGCACTCTCC. Аналогично b.seqсодержит ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. Короче говоря, Column1 следует использовать в качестве имени выходного файла с расширением, .seqа затем в нем должна быть соответствующая последовательность column2. Я могу сделать это, написав скрипт на Perl, но все что угодно в командной строке будет полезно. Надеюсь скоро услышать.

user3138373
источник

Ответы:

16

Мой быстрый ответ был бы, awkно если вы обрабатываете много строк - а я говорю о миллионах - вы, вероятно, увидите реальную выгоду от перехода на «настоящий» язык программирования.

Имея это в виду (и awkуже принятый за ответ), я написал несколько реализаций на разных языках и сравнил их с одним и тем же набором данных из 10 000 строк на 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

С первого взгляда C выглядит лучше, но это была свинья, которая бегала так быстро. Pypy и C ++ намного проще писать и работать достаточно хорошо, если вы не говорите о многомиллиардных строках. Если бы это было так, обновление до выполнения всего этого в ОЗУ или на SSD могло бы быть более выгодным вложением, чем улучшение кода.

Очевидно, что за то время, которое я потратил на их изучение, вы могли бы обработать несколько сотен миллионов записей в самом медленном варианте . Если вы можете писать только awkпетли или циклы Bash, делайте это и продолжайте жить. У меня явно было слишком много свободного времени сегодня.

Я также протестировал некоторые многопоточные опции (в 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, но после блога об этом я получил некоторую помощь от Джулиана Клода . Эта версия теперь самая быстрая из-за улучшенных буферов ввода-вывода. Это также намного дольше и сложнее, чем все остальное.

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

(Не в версии 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
Oli
источник
2
Я должен отметить, что перечисление всех ваших вариантов производительности может быть столь же расточительным, как и простое первое, что приходит на ум. awkвсе еще хороший ответ для чего-то меньшего, чем десятки миллионов. Даже если вы [линейно] масштабируете это до миллиарда строк, C сэкономит вам всего 1,5 часа на Perl и 3,6 часа на awk.
Оли
Теперь мой C ++ версии на есть так гораздо быстрее, может быть , я бы рассмотреть C ++ для более простой обработки текста огромных наборов данных. Это почти в два раза быстрее, и это разница во времени, когда вы получаете миллиарды линий.
Оли
7
xkcd.com/1445
mr.spuratic
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
jasonwryan
источник
Привет, это работает. Большое спасибо. Можете немного объяснить код?
user3138373
@ user3138373 Надеюсь, это поможет ...
jasonwryan
Это помогает .. Спасибо Почему не печатать работу вместо printf ??
user3138373
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 вывел все необработанные команды и перенаправил вывод в "sh"
Гленн Джекман
1
@glennjackman: Это был просто интересный альтернативный способ сделать это. Если ввод большой, awkвероятно, самый эффективный инструмент для использования. Вы, конечно, правы в том, что не появлялись shдля каждой строки, я добавил опцию pipe в качестве альтернативы.
Тор