將大檔案分割成區塊而不分割條目

將大檔案分割成區塊而不分割條目

我有一個相當大的 .msg 文件,格式為 UIEE 格式。

$ wc -l big_db.msg
8726593 big_db.msg

本質上,該文件由不同長度的條目組成,如下所示:

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

這是由空白行分隔的兩個條目的範例。我希望將這個大文件拆分為較小的文件,而不將一個條目分成兩個文件。

文件中的每個單獨條目均由換行符號(完全空白的行)分隔。我希望將這個 870 萬行檔案分成 15 個檔案。我知道存在類似的工具split,但我不太確定如何拆分文件,但只能在換行符上拆分它,這樣單個條目就不會分解為多個文件。

答案1

使用以下建議csplit

根據行號分割

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

例子

假設我有一個包含 1000 行的檔案。

$ seq 1000 > file.txt

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

結果像這樣的文件:

$ 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

您可以提前根據特定文件中的行數預先計算數字來繞過必須指定重複次數的靜態限制。

$ 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

根據空白行分割

另一方面,如果您想簡單地在文件中包含的空白行上拆分文件,您可以使用以下版本split

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

例子

假設我在上面添加了 4 個空白行file.txt,並將文件創建為file2.txt.您可以看到它們已手動添加,如下所示:

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

21
--
72

73
--
112

113
--
178

179

上面顯示我已將它們添加到範例文件中的相應數字之間。現在當我運行csplit命令時:

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

你可以看到我現在有 4 個文件,它們已經根據空白行分割開了:

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

參考

答案2

如果您不關心記錄的順序,您可以這樣做:

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

否則,您需要先取得記錄數,以了解每個輸出檔案中應放入多少筆記錄:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in

答案3

這是一個可行的解決方案:

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

它的工作原理是允許第一個sed編寫第二個sed的腳本。第二個sed先收集所有輸入行,直到遇到空白行。然後它將所有輸出行寫入檔案。第一個sed為第二個寫出一個腳本,指示它在哪裡寫入輸出。在我的測試案例中,腳本如下所示:

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

我是這樣測試的:

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

這為我提供了一個 6000 行的文件,如下所示:

<iteration#>
and
more
lines
here
#blank

...重複1000次。

運行上面的腳本後:

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

輸出

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

答案4

嘗試awk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg

相關內容