Программирование Awk: Разделите большой файл на меньшие на основе шаблона

У меня есть большой файл input.dat, который смотрит как показано ниже.

kpoint1 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000
 kpoint2 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

Я должен Разделить файл на 2 меньших как ниже

kpoint1.dat:

kpoint1 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

и kpoint2.dat:

kpoint1 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

Я записал маленький сценарий, чтобы сделать это. Сценарий показывают ниже.

for j in {1..2} 
do
    awk '$1=="kpoint'$j'" {for(i=1; i<=3; i++){getline; print}}' tmp7 >kpoint'$j'.dat
done

Сценарий создает выходные файлы с желаемыми именами. Но все файлы пусты. Кто-либо может помочь мне решить это?

4
задан 19 May 2017 в 11:24

2 ответа

Это может быть сделано полностью в awk:

$ awk '$1 ~ /kpoint[0-9]/ { file = $1 ".dat" } {print > file}' file
$ head kpoint*
==> kpoint1.dat <==
kpoint1 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

==> kpoint2.dat <==
 kpoint2 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

Awk также поддерживает > file для перенаправления с некоторыми тонкими различиями (см. руководство awk's GNU для больше).

3
ответ дан 1 December 2019 в 09:32

В то время как ответ muru является самым простым, существует несколько других путей без использования awk.

Perl

Подход с awk состоит в основном в том, что мы пишем в определенное имя файла и изменяем то имя файла, если и только если мы встречаемся с kpoint в начале строки. Тот же подход может быть сделан с Perl:

$ perl -ane '$p=$F[0] if $F[0] =~ /kpoint/;open($f,">>",$p . ".dat"); print $f $_' input.txt

Вот то, как это работает:

  • -a флаг позволяет нам использовать специальное предложение @F массив слов, которые были автоматически разделены от каждой строки входного файла. Таким образом $F[0] относится к первому слову, точно так же, как $1 в awk
  • $p=$F[0] if $F[0] =~ /kpoint/ предназначен для изменения $p (который предназначен, чтобы быть переменной префикса), если и только если kpoint находится в строке. Улучшение того соответствия шаблона могло быть /^ *kpoint/
  • при каждом повторении мы открываемся для добавления файла, который имеет имя $p присоединенный с .dat строка; обратите внимание, что добавление части важно. Если Вы хотите иметь четкое выполнение, Вы, вероятно, хотите избавиться от старых kpoint файлы. Если мы хотим, чтобы файл всегда был создан новый и перезаписан, то мы можем reqrite исходная команда как:

    $ perl -ane 'if ($F[0] =~ /kpoint/){$p=$F[0]; open($f,">",$p . ".dat")}; print $f $_' input.txt
    
  • И наконец print $f $_ просто печать к любому имени файла мы имеем открытый.

разделение

От Вашего примера кажется, что каждая запись состоит из 5 строк. Если это постоянно, мы можем разделить файл тот путь, не полагаясь на сопоставление с образцом с split. Конкретно эта команда:

$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt  kpoint

В этой команде опции следующие:

  • --additional-suffix=".dat" помехи .dat суффикс, который будет добавлен к каждому созданному файлу
  • --numeric-suffixes=1 позволит нам добавление, изменяющее числа, запускающиеся в 1 к каждому имени файла
  • -l 5 позволит разделять входной файл каждые 5 строк
  • input.txt файл, который мы пытаемся разделить
  • kpoint будет статический префикс имени файла

И здесь как это работает на практике:

$ split --additional-suffix=".dat" --numeric-suffixes=1 -l 5 input.txt  kpoint                                                                        
$ cat kpoint01.dat                                                                                                                                    
kpoint1 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000
$ cat kpoint02.dat                                                                                                                                    
 kpoint2 :       0.0000    0.0000    0.0000
  band No.  band energies     occupation 
      1     -52.8287      2.00000
      2     -52.7981      2.00000
      3     -52.7981      2.00000

Дополнительно, мы могли также добавить --suffix-length=1 сохранять длину каждого числового суффикса короче как kpoint1 вместо kpoint01, но это могло бы быть проблемой, если у Вас есть большое количество kpoints.

альтернатива awk

Этот подобен ответу muru, кроме здесь мы используем другое соответствие шаблона, а также другой подход создания переменной имени файла через sprintf()

$ awk '/^\ *kpoint/{f=sprintf("%s.dat",$1)};{print > f}' input.txt

Python

В то время как awk и split подходы короче, другие инструменты, такие как Python хорошо подходят для обработки текста, и мы можем использовать их для реализации большего количества подробных, но рабочих решений.

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

#!/usr/bin/env python3
import sys

def write_entry(pref,line_list):
    # this function writes the actual file for each entry
    with open(".".join([pref,"dat"]),"w") as entry_file:
        entry_file.write("".join(line_list))

def main():
    prefix = ""
    old_prefix = ""
    entry=[]
    with open(sys.argv[1]) as fd:
        for line in fd:
            # if we encounter kpoint string, that's a signal
            # that we need to write out the list of things 
            if line.strip().startswith('kpoint'):
                prefix=line.strip().split()[0]
                # This if statement counters special case
                # when we just started reading the file
                if not old_prefix:
                    old_prefix = prefix
                    entry.append(line)
                    continue
                write_entry(old_prefix,entry)
                old_prefix = prefix
                entry=[]
            # Keep storing lines. This works nicely after old 
            # entry has been cleared out. 
            entry.append(line)
    # since we're looking backwards, we need one last call
    # to write last entry when input file has been closed
    write_entry(old_prefix,entry)

if __name__ == '__main__': main()

Чистый Bash

Почти та же идея как подход Perl - мы продолжаем писать все в определенное имя файла и изменяем имя файла только, когда мы находим строку с kpoint в нем.

#!/usr/bin/env bash

while IFS= read -r line;
do
    case "$line" in
        # We found next entry. Use word-splitting to get
        # filename into fname variable, and truncate that filename
        *kpoint[0-9]*) read fname trash <<< $line  && 
                       echo "$line" > "$fname".dat ;;
        # That's just a line within entry. Append to 
        # current working file
        *) echo "$line" >> "$fname".dat ;;
    esac
done < "$1"

# Just in case there are trailing lines that weren't processed
# in while loop, append them to last filename
[ -n "$line" ] && echo "$line" >> "$fname".dat ;
2
ответ дан 1 December 2019 в 09:32

Другие вопросы по тегам:

Похожие вопросы: