Используя awk sed et al. для анализа полей из файла без конечных тегов

Мой желаемый результат - файл с разделителями-запятыми. Если ничего не помогает, я знаю, что могу написать сценарий с логикой for/each или чем-то еще, но я надеюсь найти элегантное решение с использованием awk и sed, которые уже много раз хорошо мне служили. Я просто в тупике с этим.

Ниже представлено представление данных, за которым следует желаемый результат. Примечание: каждый GROUPNUMBER имеет связанный набор полей GROUPMEMBER, GROUPMEMBERID и MEMBERRANK (которые не всегда располагаются на первом символе строки, как в примере данных). Другие строки и текст, окружающие GROUPNUMBER, не важны, могут также иметь двоеточие и представлены здесь как текст lorem ipsum. Некоторые GROUPNUMBER вообще не имеют связанных полей (как видно из примера данных в последних строках для группы № 88888). Кроме того, в примере данных разделы GROUPNUMBER показаны разделенными пустыми строками, но это не всегда так (иногда следующий раздел переходит в предыдущий без разрыва строки).

Образец данных:

loremipsum: loremipsum?# loremipsum/123: loremipsumxx GROUPNUMBER:111222
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Joe:MEMBERRANK:1
GROUPMEMBERID:1234
GROUPMEMBER:Mike:MEMBERRANK:1
GROUPMEMBERID:2234
loremipsum14e3:loremispum loremipsumxxxx
loremipsum1eer534:loremispum loremipsumxxfgt
GROUPMEMBER:Sue:MEMBERRANK:89
GROUPMEMBERID:3234
GROUPMEMBER:John:MEMBERRANK:323
GROUPMEMBERID:4234:loremipsumaaa_loremipsum

loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:333444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Frank:MEMBERRANK:4
GROUPMEMBERID:5234
GROUPMEMBER:Laurie:MEMBERRANK:4
GROUPMEMBERID:6234

loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:88888
loremipsum123:loremispum loremipsumxxxx

Желаемый результат:

GROUPNUMBER, MEMBERNAME, MEMBERID, MEMBERRANK

Example from above data:
111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,

person Jason908    schedule 12.12.2019    source источник
comment
which have served me well so many times before На этом форуме мы призываем других сообщать о том, что они пробовали. Пожалуйста, опубликуйте, что вы пробовали, и покажите, что мешает вам написать это. В чем именно заключается ваш вопрос? Хотя я считаю, что это можно сделать в sed, решение на awk было бы намного проще и читабельнее.   -  person KamilCuk    schedule 12.12.2019
comment
отредактируйте свой вопрос, чтобы объяснить, как идентифицировать GROUPNUMBER:111222 и т. д. строки из всего остального текста, который вы loremipsum- изд. Например, это единственные строки в вашем вводе, которые являются all upper case then colon then digits?   -  person Ed Morton    schedule 12.12.2019


Ответы (2)


Это вряд ли возможно в GNU sed... но было бы лучше использовать что-то с хеш-таблицей или, по крайней мере, с большим количеством переменных, чем пространство хранения:

sed -nE '/GROUPNUMBER:/{s/.*://;h};/GROUPMEMBER:/{N;G;s/GROUPMEMBER:(.*):MEMBERRANK:(.*)\nGROUPMEMBERID:([^:]*).*\n(.*)/\4,\1,\2,\3/p};${g;s/$/,,,/p}' file

Это хакерская обработка последней группы... последняя строка всегда будет последним номером группы и тремя запятыми.

Суть:

  • sed -nE для печати только при указании и для разрешения групп захвата без обратной косой черты
  • h для хранения GROUPNUMBER
  • N;G в строках GROUPMEMBER, чтобы добавить следующую строку и номер группы
  • Группы захвата 1–4 переупорядочены для форматирования вывода.
  • Хак на $, последняя строка, которая использует g для получения последнего GROUPNUMBER и печати его с тремя запятыми
person stevesliva    schedule 12.12.2019

Это не особенно легко, но и не невероятно сложно. Вся интересная информация находится в полях, разделенных двоеточиями, поэтому одна часть состоит в том, чтобы awk разделить строку ввода на поля на основе двоеточий (-F:). Затем необходимо распознавать номера групп, членов группы, ранги участников и идентификаторы участников. Любая строка без соответствующей информации просто игнорируется. Поиск значений поля можно выполнить, просматривая поля в поисках совпадений с ключевым словом и возвращая поле после него в качестве значения. В приведенном ниже коде эту работу выполняет функция extractor. Также необходимо отслеживать, сколько раз был напечатан номер группы. В конце ввода или при распознавании нового номера группы, если старый номер группы был напечатан ноль раз, необходимо распечатать информацию о группе. Функция print_member печатает элемент; это экономит запись оператора printf 3 раза.

awk -F: '
function extractor(tag,   i)
{
    for (i = 1; i < NF; i++)
        if ($i ~ tag)
            return $(i + 1)
    return ""
}
function print_member()
{
    printf "%s,%s,%s,%s\n", groupnumber, groupmember, groupmemberid, memberrank
}
    /GROUPNUMBER:[0-9]+/ {
        if (groupnumber != "" && groupcount == 0)
            print_member()
        groupnumber = extractor("GROUPNUMBER")
        groupmember = ""
        memberrank = ""
        groupmemberid = ""
        groupcount = 0
    }
    /GROUPMEMBER:[^:]+:MEMBERRANK:[0-9]+/ {
        groupmember = extractor("GROUPMEMBER")
        memberrank = extractor("MEMBERRANK")
    }
    /GROUPMEMBERID:[0-9]+/ {
        groupmemberid = extractor("GROUPMEMBERID")
        print_member()
        groupcount++
    }
    END {
        if (groupcount == 0)
            print_member()
    }' data

Учитывая файл данных в вопросе (имя data), вывод:

111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,

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

loremipsum: loremipsum?# loremipsum/123: loremipsumxx GROUPNUMBER:111222:hydrangea
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Joe:MEMBERRANK:1:orchid
GROUPMEMBERID:1234
GROUPMEMBER:Mike:piscatore:MEMBERRANK:1
GROUPMEMBERID:2234
loremipsum14e3:loremispum loremipsumxxxx
loremipsum1eer534:loremispum loremipsumxxfgt
GROUPMEMBER:Sue:MEMBERRANK:89
GROUPMEMBERID:3234
GROUPMEMBER:John:MEMBERRANK:323
GROUPMEMBERID:4234:loremipsumaaa_loremipsum
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:333444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Frank:MEMBERRANK:4
GROUPMEMBERID:5234
GROUPMEMBER:Laurie:MEMBERRANK:4
GROUPMEMBERID:6234
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:88888
loremipsum123:loremispum loremipsumxxxx
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:222444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Helen Mary Ann:MEMBERRANK:1
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:222555
loremipsum123:loremispum loremipsumxxxx
loremipsum123:loremispum loremipsumxxxx

Вывод сейчас:

111222,Joe,1234,1
111222,Joe,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
222444,Helen Mary Ann,,1
222555,,,

Это выглядит разумно. Пробелы в имени Хелен Мэри Энн не имеют значения; для нее не было членского удостоверения. Группа посередине без информации тоже отображается правильно.

Очевидно, вы можете преобразовать его в пригодный для использования сценарий оболочки, поместив его в файл и заменив data на "$@", чтобы он обрабатывал имена файлов, указанные в командной строке, или считывал стандартный ввод, если таких имен нет.


Как отмечено в комментариях, приведенный выше код предполагает, что поля GROUPMEMBER и MEMBERRANK будут следовать друг за другом в строке без каких-либо случайных полей типа «ipsum lorem» между ними. Если на самом деле между ними может быть поле «ipsum lorem» (я использовал piscatore во втором наборе данных), то сценарий необходимо пересмотреть, чтобы идентифицировать GROUPMEMBER и MEMBERRANK по отдельности. Это имеет дополнительное преимущество, если входные данные содержат:

ipsum lorem:MEMBERRANK:1:ipsum lorem:GROUPMEMBER:Hailey:ipsum lorem

or

ipsum lorem:MEMBERRANK:110:ipsum lorem
hallucination:GROUPMEMBER:Julian:doldrums

тогда он будет точно распознавать информацию (с линиями в любом порядке во втором примере). GROUPMEMBERID должен быть последним из трех элементов для каждого члена с заданным GROUPNUMBER.

awk -F: '
function extractor(tag,   i)
{
    for (i = 1; i < NF; i++)
        if ($i ~ tag)
            return $(i + 1)
    return ""
}
function print_member()
{
    printf "%s,%s,%s,%s\n", groupnumber, groupmember, groupmemberid, memberrank
}
    /GROUPNUMBER:[0-9]+/ {
        if (groupnumber != "" && groupcount == 0)
            print_member()
        groupnumber = extractor("GROUPNUMBER")
        groupmember = ""
        memberrank = ""
        groupmemberid = ""
        groupcount = 0
    }
    /GROUPMEMBER:[^:]+/ {
        groupmember = extractor("GROUPMEMBER")
    }
    /MEMBERRANK:[0-9]+/ {
        memberrank = extractor("MEMBERRANK")
    }
    /GROUPMEMBERID:[0-9]+/ {
        groupmemberid = extractor("GROUPMEMBERID")
        print_member()
        groupcount++
    }
    END {
        if (groupcount == 0)
            print_member()
    }' data

Вывод сейчас:

111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
222444,Helen Mary Ann,,1
222555,,,
person Jonathan Leffler    schedule 12.12.2019
comment
это прерывается, если GROUPNUMBER следует за GROUPMEMBER, GROUPMEMBERID или MEMBERRANK в одной строке. - person jhnc; 12.12.2019
comment
@jhnc - Если данные в примере недостаточно полно иллюстрируют то, что может произойти, то да, возможно, потребуется изменить обработку. Данные примера ясно показывают, что GROUPNUMBER появляется только в строке без каких-либо других элементов. Если это не точно для полного набора данных, это необходимо указать в вопросе. Нам нужно только обратиться к тому, что показывает вопрос, или что может быть выведено. - person Jonathan Leffler; 12.12.2019
comment
в последней строке вопроса упоминается, что разделы могут следовать без разрыва строки - person jhnc; 12.12.2019
comment
@jhnc: Поскольку в преамбуле к этому в примере данных говорится, что разделы GROUPNUMBER отображаются как разделенные пустыми строками, но это не всегда так, я интерпретирую это как означающее, что пустые строки (также известные как «разрыв строки») не всегда присутствует, как показано в моем втором файле данных. Если «разрыв строки» действительно означает «новую строку» — возможная, но не обязательно правдоподобная интерпретация — тогда да, потребуется дополнительная работа. YMMV. До тех пор, пока OP не предложит, что упомянутый разрыв строки является «новой строкой», а не «пустой строкой», я не буду беспокоиться. - person Jonathan Leffler; 12.12.2019
comment
было трудно объяснить это ясно, извините за путаницу. После каждой строки есть новая строка. Я имел в виду, что лишние пустые строки (двойные новые строки?), которые можно увидеть в примерах данных, не всегда присутствуют, иногда они выглядят как данные Джонатана. Я упомянул об этом, потому что не могу полагаться на двойную новую строку для обозначения конца группы. Использование функций похоже на написание скрипта, но иметь их внутри одной команды awk просто здорово. не могу дождаться, чтобы попробовать это на работе завтра. кое-что, хотя вторая строка вашего окончательного вывода показывает Джо, но это должен быть Майк, это опечатка? - person Jason908; 12.12.2019
comment
@ Jason908 — это ошибка, вызванная введением :piscatore: между GROUPMEMBER:Mike и MEMBERRANK:1. Есть 2 возможности. Во-первых, вы никогда не найдете поле «lorem ipsum» (или piscatore) между GROUPMEMBER и MEMBERANK; другое в том, что вы могли бы. Если никогда не может быть значения, где я ставлю piscatore, то GIGO (мусор на входе, мусор на выходе). Если у вас может быть поле там, то условия для GROUPMEMBER и MEMBERRANK должны быть разделены. Он скорее механический, чем жесткий. Куда мне идти? Я подозреваю, что разрешение поля «ipsum lorem» уместно. Ваш звонок? - person Jonathan Leffler; 12.12.2019
comment
К счастью, никогда не будет поля между GROUPMEMBER и MEMBERRANK. Если что, MEMBERRANK мог вообще не появиться, но между ними не будет ничего типа piscatore - person Jason908; 12.12.2019
comment
к вашему сведению, у меня это не работает, я получаю только первого члена группы. Я работаю над этим... (GNU awk 4.1.4) - person Jason908; 13.12.2019
comment
Любопытный!! Я только что протестировал GNU Awk 4.1.3 на трех файлах данных, которые у меня есть, и оба сценария дают ожидаемые для меня результаты (результаты, показанные выше для показанных данных), и те же результаты, что и macOS (BSD) Awk. . То, что они дают один и тот же результат, меня не удивляет; Я не делаю ничего хитрого и не использую возможности конкретной версии Awk. Итак, вы копировали и вставляли или перепечатывали? - person Jonathan Leffler; 13.12.2019