Eu tenho um arquivo .msg bastante grande formatado no formato UIEE.
$ wc -l big_db.msg
8726593 big_db.msg
Essencialmente, o arquivo é composto de entradas de vários comprimentos que se parecem com isto:
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
Este é um exemplo de duas entradas, separadas por uma linha em branco. Desejo dividir este arquivo grande em arquivos menores sem dividir uma entrada em dois arquivos.
Cada entrada individual é separada por uma nova linha (uma linha completamente em branco) no arquivo. Desejo dividir esse arquivo de 8,7 milhões de linhas em 15 arquivos. Eu entendo que existem ferramentas como, split
mas não tenho certeza de como dividir o arquivo, mas apenas dividi-lo em uma nova linha para que uma única entrada não seja dividida em vários arquivos.
Responder1
Usando a sugestão de csplit
:
Divisão com base em números de linha
$ csplit file.txt <num lines> "{repetitions}"
Exemplo
Digamos que eu tenha um arquivo com 1000 linhas.
$ seq 1000 > file.txt
$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405
resulta em arquivos como este:
$ 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
Você pode contornar a limitação estática de ter que especificar o número de repetições pré-calculando os números com base no número de linhas em seu arquivo específico com antecedência.
$ 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
Divisão com base em linhas em branco
Se, por outro lado, você quiser simplesmente dividir um arquivo em linhas em branco contidas no arquivo, você pode usar esta versão de split
:
$ csplit file2.txt '/^$/' "{*}"
Exemplo
Digamos que eu adicionei 4 linhas em branco acima file.txt
e crie o arquivo file2.txt
. Você pode ver que eles foram adicionados manualmente assim:
$ grep -A1 -B1 "^$" file2.txt
20
21
--
72
73
--
112
113
--
178
179
O texto acima mostra que eu os adicionei entre os números correspondentes em meu arquivo de amostra. Agora, quando executo o csplit
comando:
$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290
Você pode ver que agora tenho 4 arquivos que foram divididos com base na linha em branco:
$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179
Referências
Responder2
Se você não se importa com a ordem dos registros, você poderia fazer:
gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in
Caso contrário, você precisaria primeiro obter o número de registros, para saber quantos colocar em cada arquivo de saída:
gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
{printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in
Responder3
Aqui está uma solução que pode funcionar:
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
Funciona permitindo que o primeiro sed
escreva o sed
script do segundo. O segundo sed
primeiro reúne todas as linhas de entrada até encontrar uma linha em branco. Em seguida, ele grava todas as linhas de saída em um arquivo. O primeiro sed
escreve um script para o segundo, instruindo-o sobre onde escrever sua saída. No meu caso de teste, esse script ficou assim:
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
Eu testei assim:
printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file
Isso me forneceu um arquivo de 6.000 linhas, que ficou assim:
<iteration#>
and
more
lines
here
#blank
...repetido 1000 vezes.
Depois de executar o script acima:
set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
echo $splitfile
wc -l <$splitfile
tail -n6 $splitfile
done
SAÍDA
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
Responder4
Tentarawk
awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg