Pisahkan file besar menjadi potongan tanpa membelah entri

8

Saya memiliki file .msg yang agak besar yang diformat dalam format UIEE.

$ wc -l big_db.msg
8726593 big_db.msg

Pada dasarnya, file terdiri dari entri dengan panjang berbeda yang terlihat seperti ini:

UR|1
AA|Condon, Richard
TI|Prizzi's Family
CN|Collectable- Good/Good
MT|FICTION
PU|G.P. Putnam & Sons
DP|1986
ED|First Printing.
BD|Hard Cover
NT|0399132104
KE|MAFIA
KE|FICTION
PR|44.9
XA|4
XB|1
XC|BO
XD|S

UR|10
AA|Gariepy, Henry
TI|Portraits of Perseverance
CN|Good/No Jacket
MT|SOLD
PU|Victor Books
DP|1989
BD|Mass Market Paperback
NT|1989 tpb g 100 meditations from the Book of Job "This book...help you
NT| persevere through the struggles of your life..."
KE|Bible
KE|religion
KE|Job
KE|meditations
PR|28.4
XA|4
XB|5
XC|BO
XD|S

Ini adalah contoh dari dua entri, dipisahkan oleh baris kosong. Saya ingin membagi file besar ini menjadi file yang lebih kecil tanpa memecah entri menjadi dua file.

Setiap entri individual dipisahkan oleh baris baru (baris yang benar-benar kosong) dalam file. Saya ingin memecah 8,7 juta file baris ini menjadi 15 file. Saya mengerti bahwa alat-alat seperti splitada tetapi saya tidak yakin bagaimana cara membagi file tetapi hanya membaginya pada baris baru sehingga satu entri tidak bisa dipecah menjadi beberapa file.

pengguna2036066
sumber
csplitjuga ada.
mikeserv
Bisakah Anda membuat file sementara?
Braiam
@Braiam, tidak yakin apa yang Anda maksud tapi saya pikir begitu. Saya memiliki akses penuh melalui sistem file.
user2036066
maksudnya membuat file yang digunakan sementara untuk proses
polym
1
Mengapa tepatnya 15 file, jika saya boleh bertanya? Adalah awalan sebelum pipa |(seperti UR, AA, TI) relevan untuk hitungan file, bahkan sama tepatnya?
polym

Jawaban:

2

Inilah solusi yang bisa bekerja:

seq 1 $(((lines=$(wc -l </tmp/file))/16+1)) $lines |
sed 'N;s|\(.*\)\(\n\)\(.*\)|\1d;\1,\3w /tmp/uptoline\3\2\3|;P;$d;D' |
sed -ne :nl -ne '/\n$/!{N;bnl}' -nf - /tmp/file

Ini bekerja dengan memungkinkan yang pertama seduntuk menulis sedskrip kedua . Yang sedpertama mengumpulkan semua jalur input sampai bertemu dengan baris kosong. Itu kemudian menulis semua jalur output ke file. Yang pertama sedmenulis naskah untuk yang kedua menginstruksikannya di mana untuk menulis hasilnya. Dalam kasus pengujian saya skrip itu terlihat seperti ini:

1d;1,377w /tmp/uptoline377
377d;377,753w /tmp/uptoline753
753d;753,1129w /tmp/uptoline1129
1129d;1129,1505w /tmp/uptoline1505
1505d;1505,1881w /tmp/uptoline1881
1881d;1881,2257w /tmp/uptoline2257
2257d;2257,2633w /tmp/uptoline2633
2633d;2633,3009w /tmp/uptoline3009
3009d;3009,3385w /tmp/uptoline3385
3385d;3385,3761w /tmp/uptoline3761
3761d;3761,4137w /tmp/uptoline4137
4137d;4137,4513w /tmp/uptoline4513
4513d;4513,4889w /tmp/uptoline4889
4889d;4889,5265w /tmp/uptoline5265
5265d;5265,5641w /tmp/uptoline5641

Saya mengujinya seperti ini:

printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file

Ini memberi saya file 6000 baris, yang terlihat seperti ini:

<iteration#>
and
more
lines
here
#blank

... diulang 1000 kali.

Setelah menjalankan skrip di atas:

set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
    echo $splitfile
    wc -l <$splitfile
    tail -n6 $splitfile
done    

KELUARAN

15 total splitfiles
/tmp/uptoline1129
378
188
and
more
lines
here

/tmp/uptoline1505
372
250
and
more
lines
here

/tmp/uptoline1881
378
313
and
more
lines
here

/tmp/uptoline2257
378
376
and
more
lines
here

/tmp/uptoline2633
372
438
and
more
lines
here

/tmp/uptoline3009
378
501
and
more
lines
here

/tmp/uptoline3385
378
564
and
more
lines
here

/tmp/uptoline3761
372
626
and
more
lines
here

/tmp/uptoline377
372
62
and
more
lines
here

/tmp/uptoline4137
378
689
and
more
lines
here

/tmp/uptoline4513
378
752
and
more
lines
here

/tmp/uptoline4889
372
814
and
more
lines
here

/tmp/uptoline5265
378
877
and
more
lines
here

/tmp/uptoline5641
378
940
and
more
lines
here

/tmp/uptoline753
378
125
and
more
lines
here
mikeserv
sumber
3

Menggunakan saran dari csplit:

Pemisahan berdasarkan nomor baris

$ csplit file.txt <num lines> "{repetitions}"

Contoh

Katakanlah saya punya file dengan 1000 baris di dalamnya.

$ seq 1000 > file.txt

$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405

menghasilkan file seperti ini:

$ wc -l xx*
  99 xx00
 100 xx01
 100 xx02
 100 xx03
 100 xx04
 100 xx05
 100 xx06
 100 xx07
 100 xx08
 101 xx09
   1 xx10
1001 total

Anda dapat mengatasi batasan statis karena harus menentukan jumlah pengulangan dengan pra-kalkulasi angka berdasarkan jumlah baris dalam file tertentu Anda sebelumnya.

$ lines=100
$ echo $lines 
100

$ rep=$(( ($(wc -l file.txt | cut -d" " -f1) / $lines) -2 ))
$ echo $rep
8

$ csplit file.txt 100 "{$rep}"
288
400
400
400
400
400
400
400
400
405

Pemisahan berdasarkan garis kosong

Jika di sisi lain Anda hanya ingin membagi file pada baris kosong yang terkandung dalam file Anda dapat menggunakan versi split:

$ csplit file2.txt '/^$/' "{*}"

Contoh

Katakanlah saya telah menambahkan 4 baris kosong ke file.txtatas, dan buat file file2.txt. Anda dapat melihat bahwa mereka telah ditambahkan secara manual seperti:

$ grep -A1 -B1 "^$" file2.txt
20

21
--
72

73
--
112

113
--
178

179

Di atas menunjukkan bahwa saya telah menambahkan mereka di antara angka-angka yang sesuai dalam file sampel saya. Sekarang ketika saya menjalankan csplitperintah:

$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290

Anda dapat melihat bahwa saya sekarang memiliki 4 file yang telah dibagi berdasarkan baris kosong:

$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179

Referensi

slm
sumber
Saya mengedit OP dengan upaya saya untuk menggunakan ini dan saya tidak bisa membuatnya bekerja.
user2036066
File tidak terpecah pada baris baru yang kosong, yang saya coba selesaikan.
user2036066
@ user2036066 - Anda ingin membagi file menjadi 15 potongan file memastikan tidak ada pemisahan pada sebagian garis atau sesuatu yang lain?
slm
@ user2036066 - tunggu jadi file tersebut memiliki 14-15 baris yang benar-benar kosong yang ingin Anda bagi?
slm
Mengedit op lagi dengan lebih banyak konteks @slm
user2036066
3

Jika Anda tidak peduli dengan pesanan catatan, Anda bisa melakukannya:

gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in

Jika tidak, Anda harus terlebih dahulu mendapatkan jumlah catatan terlebih dahulu, untuk mengetahui berapa banyak yang dimasukkan ke dalam setiap file output:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in
Stéphane Chazelas
sumber
Menggunakan awk untuk membagi pada baris kosong adalah pemikiran pertama saya juga - +1
godlygeek
Apa file.indan file.out?
mikeserv
1

Jika Anda ingin memecah hanya pada akhir baris, Anda harus dapat melakukannya dengan -lopsi untuk split.

Jika Anda ingin membagi pada baris kosong ( \n\n), berikut adalah cara saya melakukannya di ksh. Saya belum mengujinya, dan mungkin itu tidak ideal, tetapi sesuatu di sepanjang baris ini akan berfungsi:

filenum=0
counter=0
limit=580000

while read LINE
do
  counter=counter+1

  if (( counter >= limit ))
  then
    if [[ $LINE == "" ]]
    then
      filenum=filenum+1
      counter=0
    fi
  fi

  echo $LINE >>big_db$filenum.msg
done <big_db.msg
hornj
sumber
1
Mungkin saya salah membaca, tetapi op bertanya bagaimana cara membagi \n\n, saya pikir.
mikeserv
Itu tidak benar-benar membantu saya karena itu masih akan membagi file mid-entry. Saya membutuhkannya sehingga file hanya akan dipisah pada baris kosong.
user2036066
Ya saya salah baca, maaf. Ini mungkin bukan cara terbaik, saya hanya akan membaca dalam file asli menjadi satu lingkaran dengan penghitung berapa banyak baris yang telah Anda lewati, dan sekali Anda menekan angka yang ingin Anda bagi pada mulai menghasilkan ke file baru di berikutnya garis kosong.
hornj
Mencoba untuk menguji skrip ini sekarang.
user2036066
1
Saya pikir OP tidak menanyakan bagaimana cara membagi \n\n, tetapi bukan untuk membagi di tengah garis. Dia menyebut baris baru sebagai garis kosong.
polym
0

Mencoba awk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg
dchirikov
sumber
Mencoba solusi ini sekarang
user2036066
2
Solusi ini membuat file baru untuk setiap entri, yang bukan yang saya inginkan sama sekali.
user2036066
0

Jika Anda tidak peduli dengan urutan catatan tetapi Anda khususnya tentang mendapatkan sejumlah file output, jawaban Stephane adalah cara saya akan pergi. Tapi saya merasa Anda mungkin lebih peduli menentukan ukuran yang setiap file output tidak boleh melebihi. Itu sebenarnya membuatnya lebih mudah karena Anda dapat membaca file input Anda dan mengumpulkan catatan sampai Anda mencapai ukuran itu, dan kemudian mulai file output baru. Jika itu cocok untuk Anda, sebagian besar bahasa pemrograman dapat menangani tugas Anda dengan skrip pendek. Berikut ini adalah implementasi awk:

BEGIN {
    RS = "\n\n"
    ORS = "\n\n"
    maxlen = (maxlen == 0 ? 500000 : maxlen)
    oi = 1
}

{
    reclen = length($0) + 2
    if (n + reclen > maxlen) {
        oi++
        n = 0
    }
    n += reclen
    print $0 > FILENAME"."oi
}

Masukkan ini ke dalam file, katakan program.awk, dan jalankan dengan awk -v maxlen=10000 -f program.awk big_db.msgnilai nilainya maxlenadalah yang paling Anda inginkan dalam satu file. Ini akan menggunakan 500k sebagai default.

Jika Anda ingin mendapatkan sejumlah file, mungkin cara termudah adalah dengan membagi ukuran file input Anda dengan jumlah file yang Anda inginkan, dan kemudian menambahkan sedikit ke nomor yang ingin Anda dapatkan maxlen. Misalnya, untuk mendapatkan 15 file dari 8726593 byte Anda, bagilah dengan 15 untuk mendapatkan 581773, dan tambahkan beberapa, jadi mungkin berikan maxlen=590000atau maxlen=600000. Jika Anda ingin melakukan ini berulang-ulang, Anda mungkin dapat mengkonfigurasi program untuk melakukannya.

David Z
sumber