Metode cepat memisahkan string dari textfile?


11

Saya memiliki dua file teks: string.txt dan lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Saya ingin mendapatkan file

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz

Saya bekerja dengan sekitar 28.000 entri dan mereka bervariasi antara 200 dan 56.000 karakter.

Saat ini, saya menggunakan:

start=1
end=0
i=0
while read read_l
do
    let i=i+1
    let end=end+read_l
    echo -e ">Entry_$i" >>outfile.txt
    echo "$(cut -c$start-$end String.txt)" >>outfile.txt
    let start=start+read_l
    echo $i
done <lengths.txt

Tetapi ini sangat tidak efisien. Ada ide yang lebih baik?


Bagaimana dengan ... str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txtsepertinya cukup cepat seperti yang dilakukan oleh shell ..
heemayl

Tidak terlalu cepat untuk jujur. Masih butuh waktu yang cukup lama. Saya cukup baru di linux / pemrograman jadi jika Anda berpikir ada metode yang lebih cepat tidak hanya menggunakan shell, saya terbuka untuk ide.
user3891532

4
Coba { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
jimmij

@jimmij, bagaimana kalau
mencantumkannya

Jawaban:


7

Anda dapat melakukan

{
  while read l<&3; do
    {
      head -c"$l"
      echo
    } 3<&-
  done 3<lengths.txt
} <String.txt

Dibutuhkan beberapa penjelasan:

Gagasan utamanya adalah menggunakan { head ; } <filedan diturunkan dari jawaban @mikeserv yang diremehkan . Namun dalam hal ini kita perlu menggunakan banyak heads, sehingga whileloop diperkenalkan dan sedikit penyesuaian dengan deskriptor file untuk meneruskan ke headinput dari kedua file (file String.txtsebagai file utama untuk diproses dan baris dari length.txtsebagai argumen ke -copsi) . Idenya adalah bahwa manfaat dalam kecepatan harus datang dari tidak perlu mencari melalui String.txtsetiap kali perintah suka headatau cutdipanggil. The echohanya untuk mencetak baris baru setelah setiap iterasi.

Betapa lebih cepat (jika ada) dan menambahkan di >Entry_iantara baris dibiarkan sebagai latihan.


Penggunaan redirection I / O dengan rapi. Karena tag tersebut adalah Linux, Anda dapat menganggap bahwa shell tersebut adalah Bash dan gunakan read -u 3untuk membaca dari deskriptor 3.
Jonathan Leffler

@ JonathanLeffler, Linux tidak ada hubungannya dengan bash. Sebagian besar sistem berbasis Linux tidak bashdiinstal (pikirkan Android dan sistem tertanam lainnya). bashmenjadi shell paling lambat dari semuanya, beralih ke bash kemungkinan akan menurunkan kinerja lebih signifikan daripada keuntungan kecil yang dibawa dari beralih read <&3ke read -u3(yang dalam hal apa pun akan tidak signifikan dibandingkan dengan biaya menjalankan perintah eksternal seperti head). Beralih ke ksh93 yang memiliki headbuiltin (dan yang mendukung opsi non-standar -c) akan meningkatkan kinerja lebih banyak.
Stéphane Chazelas

Perhatikan bahwa argumen head -c(untuk headimplementasi di mana opsi non-standar tersedia) adalah sejumlah byte, bukan karakter. Itu akan membuat perbedaan dalam multi-byte locales.
Stéphane Chazelas

7

Secara umum, Anda tidak ingin menggunakan shell loop untuk memproses teks . Di sini, saya akan menggunakan perl:

$ perl -lpe 'read STDIN,$_,$_; print ">Entry_" . ++$n' lengths.txt < string.txt
>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz

Itu satu perintah, yang berbunyi (dengan buffering jadi jauh lebih efisien daripada perintah shell readyang membaca satu byte (atau beberapa byte untuk file biasa) sekaligus) kedua file hanya sekali (tanpa menyimpannya dalam memori penuh), begitu juga akan ada beberapa urutan besarnya lebih efisien daripada solusi yang menjalankan perintah eksternal dalam satu loop shell.

(tambahkan -Copsi jika angka-angka itu haruslah jumlah karakter di lokal saat ini sebagai lawan dari jumlah byte. Untuk karakter ASCII seperti dalam sampel Anda, itu tidak akan membuat perbedaan).


Itu penggunaan kembali yang berbelit-belit $_sebagai parameter output dan input read, tetapi mengurangi jumlah byte dalam skrip.
Jonathan Leffler

Dalam tes cepat (sampel OP diulang 100000 kali), saya menemukan solusi ini sekitar 1200 kali lebih cepat dari @ jimmij (0,3 detik vs 6 menit (dengan bash, 16 detik dengan PATH=/opt/ast/bin:$PATH ksh93)).
Stéphane Chazelas

6

bash, versi 4

mapfile -t lengths <lengths.txt
string=$(< String.txt)
i=0 
n=0
for len in "${lengths[@]}"; do
    echo ">Entry_$((++n))"
    echo "${string:i:len}"
    ((i+=len))
done

keluaran

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz

4

Bagaimana dengan awk?

Buat file yang disebut process.awkdengan kode ini:

function idx(i1, v1, i2, v2)
{
     # numerical index comparison, ascending order
     return (i1 - i2)
}
FNR==NR { a[FNR]=$0; next }
{ i=1;PROCINFO["sorted_in"] = "idx";
        for (j in a) {
                print ">Entry"j;
                ms=substr($0, i,a[j])
                print ms
                i=i+length(ms)
        }
}

Simpan dan jalankan awk -f process.awk lengths.txt string.txt


Berdasarkan penggunaan PROCINFO, ini bukan standar awk, tetapi gawk. Dalam hal ini saya lebih suka yang lain gawkhanya fitur, FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
manatwork
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.