Разделение большого файла с помощью awk на куски с определенным количеством многострочных записей - PullRequest
0 голосов
/ 14 сентября 2018

Я хочу разделить большой файл (> 15G, несколько миллионов записей) на более мелкие фрагменты с определенным количеством записей.Я использую Ubuntu 16.04.

Вот правила:

  1. В случае проблем с переносимостью я хотел бы придерживаться команд UNIX.
  2. Существует определенный шаблон, определяющий конец каждой записи('$$$$') во входном файле.
  3. Этот шаблон должен быть сохранен для отдельных записей в чанах
  4. Каждый чанк должен содержать n записей
  5. Каждая запись можетразличаются по числу строк.

Я искал похожие вопросы как этот , но не смог найти именно то, что искал.

Вотпример синтаксиса входного файла.

example.sdf

Item1
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
3

$$$$
Element2
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
5

$$$$
Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$

Требуемый вывод для n = 2:

example.sdf.chunk000001

Item1
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
3

$$$$
Element2
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
5

$$$$

example.sdf.chunk000002

Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$

В данный момент я пытался добиться этого с помощью split и awk (см. Ниже), но это выглядит неуклюже.Я также попытался взглянуть на csplit, но не смог найти ни одной опции для установки определенного количества записей в каждом чанке.

split

Команда split работает отлично, но не принимает разделитель $$$$, так как он содержит более одного символа.Я могу заставить его работать, заменив этот шаблон одним символом (@), но все может пойти не так, если этот другой символ будет найден в файле SDF.

# replace the separator with a dummy
sed -e 's/\$\$\$\$/@/g' export.sdf > example.sdf.tmp
# split the file (3 records) into smaller chunks (xaa, xab, ect.) with max 2 records
split -t @ -l 2 example.sdf.tmp
# replace the dummy with the proper separator
for f in xa*; do tail -n +2 $f |sed 's/@/\$\$\$\$/g' > $f.fixed; done

К сожалению, это выглядит не оченьоптимизирован для редактирования входного файла, а затем для каждого чанка, поэтому я попытался использовать вместо этого awk.

awk

Я новичок в awk, но мне удалось получить это:

awk 'NR%2==1 {x=sprintf(".chunk%06d",++i);} END {printf "%s",$0} {print>FILENAME x}' RS="\\$\\$\\$\\$" ORS="\$\$\$\$" example.sdf

Первый блок выглядит именно так, как я ищу, но во втором есть две ошибки:

example.sdf.chunk000002

[ blank line ]     
Something3
  Mrv171c009131823372D          

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$
$$$$

Как видите, естьпустая строка (которую я не мог отобразить, поэтому вместо нее я набрал [пустую строку]) в начале файла и один последний конечный шаблон в конце последнего фрагмента.Я также попробовал файл с 9 записями, я получил пустую строку в начале фрагментов 2-5 и окончательный дополнительный '$$$$' в конце фрагмента 5).

Как я могисправить это поведение, чтобы я получил ожидаемый результат?

Любая помощь будет высоко ценится!

Жозе Мануэль

Ответы [ 4 ]

0 голосов
/ 14 сентября 2018

С GNU awk для RS с несколькими символами, RT и обработки нескольких открытых файлов:

$ awk -v RS='\n[$]{4}\n' 'NR%2{out="out"++c} {print $0 RT " > " out}' file
Item1
  Mrv171c009131823372D

  2  1  0  0  0  0            999 V2000
   -3.7946    2.9241    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.9708    2.9673    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
3

$$$$
 > out1
Element2
  Mrv171c009131823372D

  2  1  0  0  0  0            999 V2000
   -3.6161    1.7634    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -2.7956    1.8496    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
5

$$$$
 > out1
Something3
  Mrv171c009131823372D

  2  1  0  0  0  0            999 V2000
   -3.0580    0.5134    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
   -3.5772    1.1545    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
M  END
> <property_1>
10

$$$$
 > out2

Просто измените " > " на > после того, как вы проверили и остались довольны результатом.

При любом awk:

awk '
    NR==1 { out="out"++c }
    { print > out }
    ($0=="$$$$") && (((++nr)%2)==0) { close(out); out="out"++c }
' file
0 голосов
/ 14 сентября 2018

Использование GNU awk:

awk -v RS='\\$\\$\\$\\$\n' -v nb=2 -v c=1 '
{
   file=sprintf("%s%s%06d",FILENAME,".chunk",c)
   printf "%s%s",$0,RT > file 
}
NR%nb==0 {c++}
' example.sdk

Разделитель записей RS для шаблона $$$$ позволяет получить полный фрагмент сразу.

Переменная nb содержит номер фрагмента для файла, а c - это номер счета для имени файла.

0 голосов
/ 14 сентября 2018

Вот небольшое обновление для решения Cortenin Limier

оригинал:

awk 'BEGIN{n_records=2; counter=0}
    { print > "file_" int(counter/n_records) ".txt";
      if($0 ~ /\$\$\$\$/){counter++}}' example.sdf

Обновление:

awk 'BEGIN{n_records=2; }
     (NR==1){ file=sprintf(FILENAME ".chunk%0.6d",counter) }
     { print > file }
     ($0=="$$$$"){ 
         close(file); 
         file=sprintf(FILENAME ".chunk%0.6d",(++counter/n_records))
     }' example.sdf

Различия:

  • любая переменная по умолчанию ZERO или пустая строка, поэтому нет необходимости определять counter=0
  • переменная file содержит имя файла, поэтому оно не генерируется на каждом шаге
  • file закрывается, когда он больше не нужен.
  • Мы проверяем, действительно ли разделитель записей находится в начале и конце строки.
  • Выходные файлы будут иметь форму FILENAME.chunknnnnnn, где FILENAME заменяется исходным файлом, называемым здесь example.sdf
0 голосов
/ 14 сентября 2018

Это должно работать:

awk 'BEGIN{n_records=2; counter=0};{print > "file_" int(counter/n_records) ".txt"; if($0 ~ /\$\$\$\$/){counter++}}' example.sdf
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...