Keluarkan bagian dari setiap baris ke file terpisah

14

Saya punya file seperti ini:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

Saya ingin membuat file a.seqyang berisi urutan AGTACTTCCAGGAACGGTGCACTCTCC. Demikian pula b.seqmengandung ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT. Singkatnya, Column1 harus digunakan sebagai nama file output dengan ekstensi .seqdan kemudian harus memiliki urutan kolom2 yang sesuai di dalamnya. Saya dapat melakukan ini dengan menulis skrip perl tetapi apa pun pada baris perintah akan membantu. Berharap untuk segera mendengar.

pengguna3138373
sumber

Jawaban:

16

Respons jepretan saya akan awktetapi jika Anda sedang memproses banyak baris - dan saya berbicara tentang jutaan - Anda mungkin akan melihat manfaat nyata dari beralih ke bahasa pemrograman "nyata".

Dengan pemikiran itu (dan awksudah diambil sebagai jawaban) saya menulis beberapa implementasi dalam bahasa yang berbeda dan membandingkannya pada set data 10.000-baris yang sama pada 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

Sepintas C terlihat paling baik tapi itu babi untuk bisa berlari secepat itu. Pypy dan C ++ jauh lebih mudah untuk menulis dan berkinerja cukup baik kecuali jika Anda berbicara tentang miliaran baris. Jika demikian, peningkatan untuk melakukan ini semua dalam RAM atau pada SSD mungkin merupakan investasi yang lebih baik daripada peningkatan kode.

Jelas dalam waktu yang saya habiskan melalui ini Anda mungkin bisa memproses beberapa ratus juta catatan dalam opsi paling lambat . Jika Anda hanya dapat menulis awkatau mengulangi Bash, lakukan itu dan lanjutkan hidup. Saya jelas punya terlalu banyak waktu luang hari ini.

Saya juga menguji beberapa opsi multi-threaded (dalam C ++ dan Python dan hibrida dengan GNU parallel) tetapi overhead threads benar-benar melebihi manfaat apa pun untuk operasi sederhana (string splitting, writing).

Perl

awk(di gawksini) akan secara jujur ​​menjadi tempat panggilan pertama saya untuk menguji data seperti ini, tetapi Anda dapat melakukan hal-hal yang serupa di Perl. Sintaks yang sama tetapi dengan pegangan tulisan yang sedikit lebih baik.

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

Python

Saya suka Python. Ini adalah pekerjaan saya sehari-hari dan hanya bahasa yang bagus, solid, dan sangat mudah dibaca. Bahkan seorang pemula mungkin bisa menebak apa yang terjadi di sini.

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

Anda harus ingat bahwa pythonbiner distribusi Anda bukan satu-satunya implementasi Python di luar sana. Ketika saya menjalankan tes yang sama melalui Pypy, itu lebih cepat daripada C tanpa optimasi logika lebih lanjut. Ingatlah itu sebelum menulis Python sebagai "bahasa lambat".

C

Saya memulai contoh ini untuk melihat apa yang benar-benar bisa saya lakukan dengan CPU, tetapi terus terang, C adalah mimpi buruk untuk dikodekan jika Anda belum menyentuhnya dalam waktu yang lama. Ini memiliki kelemahan tambahan yaitu terbatas pada garis 100-char meskipun sangat mudah untuk mengembangkannya, saya hanya tidak membutuhkannya.

Versi asli saya lebih lambat dari C ++ dan pypy tetapi setelah blogging tentang hal itu saya mendapat bantuan dari Julian Klode . Versi ini sekarang yang tercepat karena buffer IO yang di-tweak. Ini juga jauh lebih lama dan lebih terlibat daripada yang lainnya.

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

Berkinerja baik dan jauh lebih mudah untuk menulis daripada C. asli. Anda memiliki segala macam hal yang memegang tangan Anda (terutama ketika menyangkut string dan input). Semua itu berarti Anda benar-benar dapat menyederhanakan logika. strtokdi C adalah babi karena memproses seluruh string dan kemudian kita perlu melakukan semua alokasi memori yang melelahkan. Ini hanya melintas di sepanjang garis sampai menyentuh tab dan kami menarik segmen keluar saat kami membutuhkannya.

#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

(Bukan versi moreutils). Ini adalah sintaksis ringkas yang bagus tetapi OMGSLOW. Saya mungkin salah menggunakannya.

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

Generator uji harness

Inilah data-generator saya untuk 100000 baris [ATGC] * 64. Ini tidak cepat dan perbaikan sangat disambut.

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
Oli
sumber
2
Saya harus menunjukkan bahwa menghitung semua pilihan Anda untuk kinerja dapat menjadi sia-sia seperti halnya dengan hal pertama yang muncul dalam pikiran. awkmasih merupakan jawaban yang bagus untuk apapun yang kurang dari puluhan juta. Bahkan jika Anda [linear] skala ini hingga satu miliar baris, C hanya menghemat Anda 1,5 jam lebih Perl dan 3,6 jam lebih awk.
Oli
Sekarang saya C ++ versi ada di sana adalah begitu jauh lebih cepat, mungkin saya akan mempertimbangkan C ++ untuk pengolahan teks yang lebih sederhana data-set besar. Ini hampir dua kali lebih cepat dan itu perbedaan banyak jam ketika Anda mencapai miliaran garis.
Oli
7
xkcd.com/1445
mr.spuratic
1
juga: Hukum sepele Parkinson
rook
1
Saya pikir kecepatan generasi harness pengujian Anda terikat oleh generator angka acak. Anda dapat membuatnya lebih cepat dengan menggunakan setiap nomor memberikan atau menghasilkan distribusi homogen, misalnya: 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

Implementasi shell murni:

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

Menggunakan awk:

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

Dari yang dinominasikan file, cetak bidang kedua di setiap catatan ( $2) ke file bernama setelah bidang pertama ( $1) dengan .seqditambahkan ke nama.

Seperti yang ditunjukkan Thor dalam komentar, untuk dataset besar, Anda dapat menggunakan deskriptor file, jadi sebaiknya tutup setiap file setelah menulis :

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file
jasonwryan
sumber
Hai Ini berfungsi Terima kasih banyak .. Bisakah Anda jelaskan sedikit kode?
user3138373
@ user3138373 Harapan itu membantu ...
jasonwryan
Ini membantu .. Terima kasih. Mengapa tidak mencetak berfungsi daripada printf ??
user3138373
3
Jika ada banyak baris, semua deskriptor file yang tersedia akan digunakan, jadi sebaiknya Anda menambahkan a close($1".seq").
Thor
1
@Tor, benar. Beberapa awkimplementasi seperti GNU tahu bagaimana cara mengatasinya.
Stéphane Chazelas
3

Ini adalah salah satu cara Anda dapat melakukannya dengan GNU sed:

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

Atau lebih efisien, seperti yang disarankan oleh glenn jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh
Thor
sumber
1
Walaupun itu keren, itu cukup tidak efisien, harus menelurkan perintah eksternal untuk setiap baris. Akan sedikit lebih baik memiliki output sed semua perintah mentah, dan pipa output ke "sh"
glenn jackman
1
@glennjackman: Ini hanya cara alternatif yang menarik untuk melakukannya. Jika inputnya besar, awkmungkin alat yang paling efisien untuk digunakan. Anda tentu saja benar tentang tidak menelurkan shuntuk setiap baris, saya telah menambahkan opsi pipa sebagai alternatif.
Thor