UIEE 形式でフォーマットされたかなり大きな .msg ファイルがあります。
$ 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
これは、空白行で区切られた 2 つのエントリの例です。エントリを 2 つのファイルに分割せずに、この大きなファイルを小さなファイルに分割したいと考えています。
ファイル内の各エントリは、改行 (完全に空白の行) で区切られています。この 870 万行のファイルを 15 個のファイルに分割したいと考えています。このようなツールがsplit
存在することは理解していますが、ファイルを分割する方法がよくわかりません。改行でのみ分割すれば、1 つのエントリが複数のファイルに分割されることはありません。
答え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
が 2 番目sed
のスクリプトを書き込むことで機能します。2 番目のsed
スクリプトは、最初に空白行に遭遇するまですべての入力行を収集します。次に、すべての出力行をファイルに書き込みます。最初のsed
スクリプトは、2 番目のスクリプトに出力を書き込む場所を指示するスクリプトを書き出します。私のテスト ケースでは、スクリプトは次のようになりました。
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