LINUX.ORG.RU

Обучение AWK: преобразовать текст в таблицу.

 , ,


0

3

Понимаю что такая задача решается awk`ом (и возможно не за один проход), но понимания «как это реализовать» ещё не хватает. Прошу показать как это делается и если будут вопросы - объяснить.

Формат ТЕКСТА на входе:

КЛЮЧ1
категория_1
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
... (пропущенные строки соответствуют категории "1")
элемент_1. элемент_2 / элемент_3
категория_2
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
... (пропущенные строки соответствуют категории "2")
элемент_1. элемент_2 / элемент_3
Формат таблицы на выходе:
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
... (пропущенные строки соответствуют категории "1")
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
... (пропущенные строки соответствуют категории "2")
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
Категорий много, поэтому взялся за bash.

Имя категории состоит из одного слова, содержащего «_».

Под мнемоникой [tab] понимается любой допустимый делиметр полей в таблице - планируется сделать csv-таблицу.

Поле [ключ] состоит из НЕСКОЛЬКИХ СЛОВ, разделённых пробелами!

★★

Последнее исправление: zzdnx (всего исправлений: 1)

Хм, а это точно не какой-нибудь известный формат, под который есть парсеры? И элементы действительно разделяются сначала точкой, а потом косой чертой?

alozovskoy ★★★★★
()
Ответ на: комментарий от alozovskoy

A это точно не какой-нибудь известный формат, под который есть парсеры?

Не знаю. Я, можно сказать, начинающий в задаче парсинга, поэтому мне не известны стандарты и соответствующие им готовые решения. Мне довольно редко приходится обращаться к этой задаче. В 90% случаев хватает малых познаний в sed, но иногда попадаются и такие задачки, как эта.

И да, элементы действительно разделяются именно так. По крайней мере именно в этом конкретном случае.

PS

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

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 2)
BEGIN {
    #    State            | Can switch to
    # --------------------------------------
    # 0: Initial          |     1
    # 1: Parsed key       |     2, 1
    # 2: Parsed category  |     2, 3
    state = 0
    currentKey = ""
    currentCategory = ""
}

state == 0 {
    # Startup
    currentKey = $1
    state = 1
    next
}

state == 1 && $1 !~ /_/ {
    print "from key to key!"
    # Switched from key to key
    state = 1
    currentKey = $1
    next
}

state == 1 && $1 ~ /_/ {
    # Switched from key to category
    state = 2
    currentCategory = $1
    next
}

state == 2 && NF == 4 {
    # Current record is an element
    print currentKey, currentCategory, $1, $2, $4
    next
}

state == 2 && NF != 4 && $1 ~ /_/ {
    # Update category
    currentCategory = $1
    next
}

state == 2 && $1 !~ /_/ {
    # From category to key
    currentKey = $1
    next
}
/tmp $ cat text.txt 
КЛЮЧ1
категория_1
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
категория_2
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
КЛЮЧ2
категория_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
категория_4
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
элемент_1. элемент_2 / элемент_3
/tmp $ awk -f rebuild.awk text.txt 
КЛЮЧ1 категория_1 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_1 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_1 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_1 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_2 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_2 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_2 элемент_1. элемент_2 элемент_3
КЛЮЧ1 категория_2 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_3 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_3 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_3 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_3 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_4 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_4 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_4 элемент_1. элемент_2 элемент_3
КЛЮЧ2 категория_4 элемент_1. элемент_2 элемент_3
yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

Красиво. Можно ссылку на источник или учебник (желательно на русском, так как мой английский всё ещё не позволяет учиться без словаря)

zzdnx ★★
() автор топика
Ответ на: комментарий от yoghurt

1) У меня ключ содержит пробелы. В итоговую таблицу попадает не весь ключ, а только его начало (до первого пробела).

2) Этот парсер не работает в конвеере bash. Наверное, потомучто у меня в элементах присутствуют спецсимволы, типа «&», одиночных кавычек.

zzdnx ★★
() автор топика

Без более точных требований достаточно просто сделать

$ cat parser.awk 
#!/usr/bin/awk -f

BEGIN { FS = "[./ ]+"; OFS = " [tab] " }
NR == 1 { key = $0; next }
NF == 1 { category = $1; next }
{ print key, category, $1, $2, $3 }

$ ./parser.awk input.data 
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_2 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3

unterwulf
()
Ответ на: комментарий от zzdnx

1) У меня ключ содержит пробелы. В итоговую таблицу попадает не весь ключ, а только его начало (до первого пробела).

Предупреждать надо было =) currentKey = $0 тогда вместо этого. Но вообще, если тут что-то может содержать пробелы, то многое уже поломается.

2) Этот парсер не работает в конвеере bash.

Не вижу причин не работать, если его вызывать, например, как cat file | awk -f script.awk

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

cat file | awk -f script.awk - работает

Но не работает в более сложном конвеере:

cat X | sed | grep | sed | sed | awk -f

При этом, если воспользоваться временным файлом, то всё работает. Складывается впечатление, что работа awk заваливается потомучто конвеер поставляет строки на обработку не стабильным потоком, а с набольшими задержками, которые awk как-то по-своему интерпретирует.

zzdnx ★★
() автор топика
Ответ на: комментарий от unterwulf

Практически то, что было нужно, но проблема такая же: работает в простом конвеере с «cat», и не работает в потоке большого конвеера.

zzdnx ★★
() автор топика
Ответ на: комментарий от zzdnx

И sed добавьте -u. В общем, копайте в сторону буферизации. Я думаю, это оно.

salsa
()
Ответ на: комментарий от unterwulf

Не работает:

(большой конвеер) | awk
Работает:
(большой конвеер) > tmp_file
cat tmp_file | awk

zzdnx ★★
() автор топика
Ответ на: комментарий от unterwulf

Без более точных требований достаточно просто сделать

Хочу уточнить одно требование: «элемент_1» заканчикается точкой, но внутри «элемент_2» и «элемент_3» точка так же может существовать.

'FS = «[./ ]+»' указывает делиметеры. Из-за этого разваливается часть строк в таблице. Возможно-ли указать, что делиметером будет не точка, а точка со следующим за ней пробелом?

zzdnx ★★
() автор топика
Ответ на: комментарий от unterwulf

Идеально.

AWK парсит текст в таблицу со всеми требованиями.

#!/usr/bin/awk -f

BEGIN { FS = "(\\. | [/] )+"; OFS = "|" }
NR == 1 { key = $0; next }
NF == 1 { category = $1; next }
{ print key, category, $1, $2, $3 }

Единственная не решёная проблема: этот скрипт не обрабатывает результат работы сложного конвеера, но это уже другая история... Постараюсь не забыть дописать сюда полное решение, когда найду его.

Спасибо всем за помощь! Тема остаётся открытой пока я не найду способ обойти проблему буферизации.

zzdnx ★★
() автор топика
Ответ на: Идеально. от zzdnx

Если

(большой конвеер) | cat
выводит результат, то можно попробовать
(большой конвеер) | cat | awk

gorky ★★
()
Ответ на: Идеально. от zzdnx

покажи уже свой полный конвеер чтоли, иначе не ясно в чем там проблема.

это решение кстати будет работать только с GNU awk, так на всякий.

val-amart ★★★★★
()
Ответ на: комментарий от gorky

Получается примерно это:

КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
КЛЮЧ1 [tab] категория_1 [tab] элемент_1 [tab] элемент_2 [tab] элемент_3
# Вот тут должна была начаться "категория_2". 
# Это видно по солержимому элементов 1, 2 и 3 - соответствуют полям категории_2
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
# Вот тут должна была начаться "категория_3". 
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
элемент_1 [tab] элемент_2 [tab] элемент_3
# и так далее...

При этом некоторые поля получаются битыми (чвсть поля правильная, а другая часть содержит кусок поля «категория» или «ключ»)

zzdnx ★★
() автор топика
Ответ на: комментарий от val-amart

Покажи уже свой полный конвеер что-ли...

На входе HTML от wget или cat, далее:

sed 's/.div.class..name.\{13\}\(.*\)..div..div.class..text../\nguest_name \1\n/g' | 
egrep "cltx...[1-9]|guest_music|guest_name|music_r" | 
sed 's/guest_music/<\/div>music_r0<div>\n<"/g;s/<div class="music_r\(.\)">/music_r\1/g' | 
sed -e :a -e 's/<[^>]*>//g;/</N;//ba;s/\&amp\;/\&/g;s/^[ \t]*//' | 
И только теперь awk.

Подозреваю что виновен последний sed, который удалят HTML-тэги, включая многострочные.

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 3)
Ответ на: Получается примерно это: от zzdnx

Значит в категории также могут встречаться делимитеры, а ключей больше одного. В таком случае скрипт надо дорабатывать. Для доработки скрипта нужно определить уникальные символы для каждой категории строк (ключи, категории, элементы), которые не могут встречаться в других категориях строк.

Проблема с буферизацией решилась?

gorky ★★
()
Ответ на: комментарий от gorky

Скрипт надо дорабатывать...

Значит в категории также могут встречаться делимитеры, а ключей больше одного.

Это не совсем так. Для каждой обрабатываемой страницы ключ всего один, но он может содержать пробелы. То, что в названии категории могут встречаться разделители - я уже обошёл настройкой сценария awk.

Сейчас awk-обработчик работает, если получает данные на вход из временного файла, который подготавливается конвеером.

Проблема с буферизацией ещё не решилась. Не помогли «sed -u» и «grep --line-buffered». Писать множество временных фойлов - не лучшая идея, поэтому продолжаю копать.

zzdnx ★★
() автор топика
Последнее исправление: zzdnx (всего исправлений: 1)
Ответ на: Получается примерно это: от zzdnx

Судя по выводу — проблема в поломанных переводах строк. Например, на конце «категория_2» повис не удалённый ^M и терминал его честно отработал. Направь вывод "(большой конвеер) | awk" в файл и открой его потом в vim (cat не прокатит).

Хотя это не объясняет, почему из временного файла awk отрабатывает корректно. Система не Винда ли?

unterwulf
()
Ответ на: комментарий от unterwulf

Система не Винда ли?

повис не удалённый ^M

Так точно капитан! "(большой конвеер) | awk > tmp && vim tmp" именно он и висит...

Система не Винда ли?

Боже упаси...

zzdnx ★★
() автор топика
Ответ на: комментарий от gorky

(wget/cat) | dos2unix | (конвеер) | awk >> result_file

Теперь awk работает без проблем.

Тема закрыта. Спасибо всем за помощь!

zzdnx ★★
() автор топика
Ответ на: комментарий от unterwulf

упс, я не прав. был уверен, что ERE в FS это расширение, а сейчас открыл посикс, и действительно, в стандарте от уже 2004-го есть. когда-то давно напоролся на то что это не работало, то ли в старом Солярисе, то ли в том же OpenBSD.

val-amart ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.