Объединение нескольких наборов данных с одинаковым форматом заголовка

#linux #bash #sed #grep

#linux #bash #sed #grep

Вопрос:

У меня есть следующий файл Hello.txt с приведенными ниже данными

 HEAD 0010 YYYY
A
B
C
D
TAIL 0010 04
HEAD 0001 YYYY
A
B
C
TAIL 0001 03
HEAD 0002 YYYY
A
B
C
D
TAIL 0002 04
HEAD 0001 YYYY
A
B
C
D
E
TAIL 0001 05
HEAD 0003 YYYY
A
B
TAIL 0003 02
 

** Здесь я хочу извлечь определенные строки, соответствующие формату (HEAD 0001 и TAIL 0001), объединить записи тела вместе, исключив идентичные записи ЗАГОЛОВКА и ХВОСТА и обновив КОНЕЧНУЮ запись с помощью основного количества записей (08).
**

Ожидаемый формат вывода ниже #

 HEAD 0010 YYYY
A
B
C
D
TAIL 0010 04
HEAD 0002 YYYY
A
B
C
D
TAIL 0002 04
HEAD 0003 YYYY
A
B
TAIL 0003 02
HEAD 0001 YYYY
A
B
C
A
B
C
D
E
TAIL 0001 08
 

Комментарии:

1. Вы упомянули, что вам нужны значения HEAD 0001 to TAIL 0001 , но я тоже мог видеть значения HEAD 200 вместе с TAIL 200, не могли бы вы уточнить больше о том же?

2. ХВОСТ 0001 08 находится в ожидаемом выводе, но не на входе.

3. эй, Раман, да, цель состоит в том, чтобы объединить наборы данных для блока 0001 и иметь только один заголовок и трейлер, а также обновить трейлер с помощью объединенного количества записей тела для наборов данных 0001. Здесь первый набор данных 0001 содержит 3 записи, а второй набор данных 0001 содержит 5 записей. Обновление трейлера с комбинированным количеством записей тела = 5 3 = 8

4. Существует ли какой-либо конкретный порядок для выходных разделов?

5. @VPfB Мне очень понравился вопрос о ansible ; D

Ответ №1:

для многомерных массивов используется GNU awk:

 gawk '
    $1 == "HEAD" {key = $2; next}
    $1 == "TAIL" {next}
    {lines[key][  count[key]] = $0}
    END {
        for (key in count) {
            print "HEAD", key
            for (i=1; i<=count[key]; i  ) print lines[key][i]
            printf "TAIL %s dn", key, count[key]
        }
    }
' file
 

Ответ №2:

Вот реализация Python с использованием регулярного выражения:

 import re 

pat=r'^HEAD[ t] (d )([sS] ?)^TAIL[ t] (1)'

with open(ur_file) as f:
    data=f.read()
    blocks={}
    for block in re.finditer(pat, data, flags=re.M):
        blocks.setdefault(block.group(1), 
               []).extend([r for r in block.group(2).split('n') if r])
        
for k,v in blocks.items():
    cnt=len(v)
    data='n'.join(v)
    print(f'HEAD {k}n{data}nTAIL {k} {cnt}')
 

С принтами:

 HEAD 0010
A
B
C
D
TAIL 0010 4
HEAD 0001
A
B
C
A
B
C
D
E
TAIL 0001 8
HEAD 0002
A
B
C
D
TAIL 0002 4
HEAD 0003
A
B
TAIL 0003 2
 

Регулярное выражение r'^HEAD[ t] (d )([sS] ?)^TAIL[ t] (1)' соответствует блокам записей, как видно из этой демонстрации.

Комментарии:

1. Используете ли вы Python 3.6 для строк f?

Ответ №3:

ОБНОВЛЕНИЕ: OP изменил желаемый вывод, включив в записи требование о выводе фиксированной ширины HEAD/TAIL ; должно быть выполнимо путем изменения printf форматов; OP может настраивать по мере необходимости… используйте отрицательные числа в формате для выравнивания по левому краю, положительные числа для выравнивания по правому краю, затем отрегулируйте числа для желаемой ширины…


Предположения, основанные на образцах данных:

  • OP хочет объединить строки для определенного шаблона (например, 0001 )
  • консолидированные строки должны быть перемещены в конец вывода
  • нет необходимости (повторно) сортировать общий набор данных на основе упорядочения шаблонов (т. Е. Нет необходимости упорядочивать как 0001 , 0002 , 0003 , 0010 )

Одно awk решение:

 pattern='0001'

awk -v ptn="${pattern}" '                            # pass pattern in as awk variable "ptn"
$0 ~ "^HEAD "ptn"$" { save=1 ; next }                # start of group => set save flag; skip to next line
$0 ~ "^TAIL "ptn" " { save=0 ; next }                # end of group => clear save flag; skip to next line
save                {   i ; line[i]=$0 ; next }      # if save=1 then store current line in next position of array line[]; skip to next line
                    { print }                        # otherwise print all other lines to stdout

END { if ( i >= 1 )                                  # if we found any matches ...
         { printf "%-561s%s", "HEAD", ptn            # print the new "HEAD" line
           for ( j=1 ; j<=i ; j   )                  # then loop through our list of indices ...
               { print line[j] }                     # printing the associated line[] element
           fmt="TAIL %s in"                      # left pad the count with zero ...
           printf "%-69s%-491si" "TAIL", i, ptn   # print the "TAIL" line
         }
     }
' hello.txt
 

ПРИМЕЧАНИЕ: удалите комментарии к коду declutter

Приведенное выше генерирует:

 HEAD 0010
A
B
C
D
TAIL 0010 04
HEAD 0002
A
B
C
D
TAIL 0002 04
HEAD 0003
A
B
TAIL 0003 02
HEAD                                        0001 ('0001' starts in column 562)
A
B
C
A
B
C
D
E
TAIL       08 ('08' starts in column 70)    0001 ('0001' starts in column 562)
 

Комментарии:

1. Вам не нужен специальный if для i>99 — для больших чисел начальный 0 не печатается: awk 'BEGIN {printf "dn", 100}'

2. извините, я не понимаю, что вы говорите re : position starts at 68 ; не могли бы вы обновить вопрос, обязательно указав, где они 68 spaces отображаются во входных / выходных данных … ?

3. теперь у вас есть два набора ожидаемых выходных данных; пожалуйста, объедините их в один набор ожидаемых выходных данных, чтобы у нас было (более) четкое представление о том, что требуется; если входные данные также содержат 68/580 пробелов, то обновите и образец входных данных; вам не обязательно размещать 68/560 пробелов вкаждая строка ЗАГОЛОВКА / ХВОСТА, но, по крайней мере, сделайте что-то вроде HEAD<560 spaces>0001 и TAIL<68 spaces>0001 ; несмотря на это, я обновил ответ некоторыми идеями… по сути, вам просто нужно использовать правильное число в строке формата

Ответ №4:

РЕДАКТИРОВАТЬ: когда я опубликовал это, у вопроса был [Python] тег. Позже тег был удален.

На простом Python:

 import collections

# read into memory    
data = collections.defaultdict(list)
with open('datafile') as f:
    section = None
    for line in f:
        if line.startswith('HEAD'):
            section = line.split()[1]
        elif line.startswith('TAIL'):
            # OPTIONAL: check if section id matches
            section = None
        else:
            # OPTIONAL: check if section is not None
            data[section].append(line)

# write to output
for section, lines in data.items():
    print(f"HEAD {section}")
    for line in lines:
        print(line, end='')
    print(f"TAIL {section} {len(lines):02}")