Разделить большой файл на куски без разделения записи

8

У меня довольно большой 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

Это примеры двух записей, разделенных пустой строкой. Я хочу разбить этот большой файл на более мелкие файлы, не разбивая записи на два файла.

Каждая отдельная запись отделяется новой строкой (абсолютно пустой строкой) в файле. Я хочу разбить этот 8,7 миллионов строк файла на 15 файлов. Я понимаю, что подобные инструменты splitсуществуют, но я не совсем уверен, как разбить файл, но разделяю его только на новую строку, чтобы одна запись не разбивалась на несколько файлов.

user2036066
источник
csplitтакже существует.
mikeserv
Вы можете создавать временные файлы?
Брайам
@ Брайам, не знаю, что ты имеешь в виду, но я так думаю. У меня полный доступ через файловую систему.
user2036066 20.06.14
он имеет в виду создание файлов, которые временно используются для процесса
polym
1
Почему именно 15 файлов, если можно спросить? Являются ли префиксы перед трубой |(например UR, AA, TI) значение для подсчета файлов, даже тот же быть точным?
полим

Ответы:

2

Вот решение, которое может работать:

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
mikeserv
источник
3

Используя предложение 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

Ссылки

SLM
источник
Я отредактировал ОП с моей попыткой использовать это, и я не мог заставить его работать.
user2036066
Файл не был разбит на новую пустую строку, чего я и пытался достичь.
user2036066 20.06.14
@ user2036066 - вы хотите разделить файл на 15 файловых фрагментов, убедившись, что нет разделения на отдельные строки или что-то еще?
slm
@ user2036066 - подождите, чтобы в файле было 14-15 абсолютно пустых строк, на которые вы хотите разбить?
slm
Снова отредактировал
оперу
3

Если вы не заботитесь о порядке записей, вы можете сделать:

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
Стефан Шазелас
источник
Моей первой мыслью также было использование awk для разбиения на пустые строки - +1
godlygeek
Какие есть file.inи file.out?
mikeserv
1

Если вы хотите разделить только в конце строки, вы сможете сделать это с -lопцией для split.

Если вы хотите разделить пустую строку ( \n\n), вот как я бы это сделал в ksh. Я не проверял это, и это, вероятно, не идеально, но кое-что в этом направлении будет работать:

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
источник
1
Возможно, я неправильно прочитал, но \n\nя думаю, что оп спрашивает, как разделить на части .
mikeserv
Это не очень помогает мне, потому что это все равно расколоть середину файла. Мне нужно, чтобы файл был разбит только на пустую строку.
user2036066 20.06.14
Да, я неправильно прочитал, извините. Возможно, это не самый лучший способ, я просто прочитал бы в исходном файле цикл с счетчиком пройденного количества строк, и после того, как вы нажмете число, которое вы хотите разделить, начните выводить в новый файл на следующем пустая строка.
hornj
Попытка проверить этот скрипт прямо сейчас.
user2036066 20.06.14
1
Я думаю, что OP не спрашивает, как разделить \n\n, а скорее не разделить в середине строки. Он называет новую строку пустой строкой.
полим
0

Пытаться awk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg
dchirikov
источник
Попытка этого решения прямо сейчас
user2036066
2
Это решение создает новый файл для каждой записи, что совсем не то, что я хочу.
user2036066
0

Если вам не важен порядок записей, но вы особенно заинтересованы в получении определенного количества выходных файлов, ответ Стефана - это то, что я хотел бы сделать. Но у меня есть ощущение, что вам может быть важнее указать размер, который не должен превышать каждый выходной файл. Это на самом деле делает это проще, потому что вы можете читать входной файл и собирать записи, пока не достигнете этого размера, а затем запустить новый выходной файл. Если это работает для вас, большинство языков программирования могут справиться с вашей задачей с помощью короткого сценария. Вот реализация 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
}

Поместите это в файл, скажем program.awk, и запустите его, awk -v maxlen=10000 -f program.awk big_db.msgгде значение maxlen- это наибольшее количество байтов, которое вы хотите в любом файле. Он будет использовать 500 КБ по умолчанию.

Если вы хотите получить заданное количество файлов, возможно, самый простой способ - просто разделить размер вашего входного файла на количество файлов, которое вы хотите, а затем добавить немного к этому числу, чтобы получить maxlen. Например, чтобы получить 15 файлов из ваших 8726593 байтов, разделите на 15, чтобы получить 581773, и добавьте несколько, так что, возможно, задайте maxlen=590000или maxlen=600000. Если вы хотите сделать это многократно, можно было бы настроить программу для этого.

Дэвид З
источник