Мой быстрый ответ был бы, 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
awk
все еще хороший ответ для чего-то меньшего, чем десятки миллионов. Даже если вы [линейно] масштабируете это до миллиарда строк, C сэкономит вам всего 1,5 часа на Perl и 3,6 часа на awk.paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
.Чистая реализация оболочки:
источник
Использование
awk
:Из назначенного
file
выведите второе поле в каждой записи ($2
) в файл с именем первого поля ($1
) с.seq
добавлением к имени.Как отмечает Тор в комментариях, для большого набора данных вы можете исчерпать дескрипторы файлов, поэтому было бы целесообразно закрыть каждый файл после записи :
источник
close($1".seq")
.awk
реализации, такие как GNU, знают, как обойти это.Вот один из способов сделать это с помощью GNU sed:
Или более эффективно, как предлагает Гленн Джекман :
источник
awk
вероятно, самый эффективный инструмент для использования. Вы, конечно, правы в том, что не появлялисьsh
для каждой строки, я добавил опцию pipe в качестве альтернативы.