LINUX.ORG.RU

Разбор вывода команды и запись результатов в файл.

 


0

3

Хочу поинтересоваться у уважаемых присутствующих Гуру каким инструментом они порекомендуют решать вот такую задачу. Мне в голову несколько вариантов приходит, но затрудняюсь с выбором.

Есть команда которая читает параметры сигнала из радиомодема и показывает на экране в неудобно отформатированном виде.

qmicli -d /dev/cdc-wdm0 --nas-get-signal-strength

Не понимаю зачем было форматировать именно так,но что есть то есть. Ввыод такой:

[/dev/cdc-wdm0] Successfully got signal strength
Current:
        Network 'lte': '-73 dBm'
RSSI:
        Network 'lte': '-73 dBm'
ECIO:
        Network 'lte': '-2.5 dBm'
IO: '-106 dBm'
SINR (8): '9.0 dB'
RSRQ:
        Network 'lte': '-8 dB'
SNR:
        Network 'lte': '7.0 dB'
RSRP:
        Network 'lte': '-101 dBm'

Её можно запускать раз в несколько секунд и вот такое получать. Хочется писать это в одну строчку в CSV файл для целей последующего построения графиков например в программе grace. Чтобы в файле строчка выглядела так:

-73,-2.5,-106,9.0,-8,7.0,-101

Вобщем все цифры через запятую кроме повторяющей первой. Так как предполагается что это будет работать раз в несколько секунд то кручения многочисленных вложенных циклов хотелось бы избегать. Может вообще возможно всё это в одну длииинную строку загнать,а потом написать что-то типа форматной строки для сишного sscanf? Или еще какой-нибудь красивый способ есть?

★★

Последнее исправление: watchcat382 (всего исправлений: 1)
Ответ на: комментарий от wandrien

Да чем угодно, что умнее чем grep и sed

Вот и я на это неявно намекал в своем посте :) Но ruby это как-то уж совсем экзотично - никогда с ним дела не имел и не знаю совсем. Хотя за возможный вариант спасибо.

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

а потом написать что-то типа форматной строки для сишного sscanf? Или еще какой-нибудь красивый способ есть?

Можно натянуть сову, но мне лень.

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

Можно натянуть сову

Еще одна сова,в смысле тоже генератор парсеров по описанию грамматики: https://imatix-legacy.github.io/libero/lrdoc.htm Я даже этим пользовался. Но написать правильную грамматику сложнее чем пример выше на awk.

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

Спасибо, не знал что sed способен справиться с разбором такой сложной многострочной конструкции. Всё-таки везде написано что это строчный редактор,так я его обычно и применял,в пределах одной строки.

Я впечатлён!

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

Спасибо. Я тоже на awk хотел писать как обычно и делал для подобных случаев,но более простых. А тут у меня получалось очень уж навороченно.

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

Это YAML

Ну так и сделали бы вывод в виде имя:значение

SNR: 7.0 dB

Куда проще было бы разбирать. Нет,надо было многоэтажную конструкцию нагородить,а теперь мучайся с ней.

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

Извините,не понял аналогию. Чем плох однострочный вариант на sed,ну кроме того что очень мало кто способен такое написать и даже понять написанное не просто. Я просто не ожидал что sed может это одной командой разбирать,а не вызовом sed на каждую строку в цикле как написал бы я. Он же строчный редактор,следовательно в представлении большинства людей обрабатывает файл построчно.

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

«параметр»:«сеть»:«значение»

Сеть можно в начале указать

network: lte

Или если хочется всё-таки в каждом параметре то в одной строке именно так как вы написали

SNR: Network 'lte': '7.0 dB'

Зачем было вставлять столько переносов строк и затруднять жизнь тем кто захочет это разбирать - не знаю.

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

Задумался - а не получится ли саму команду qmcli из awk вызывать в цикле чтобы поверх этого цикл на bash не делать и не перезапускать awk на каждую итерацию?

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

а не получится ли саму команду qmcli из awk вызывать в цикле

так делать можно, но не нужно

расскажите полностью условия задачи

gagarin0
()
Последнее исправление: gagarin0 (всего исправлений: 1)
Ответ на: комментарий от watchcat382

питон. 1) малтилайн регулярка 2) разбивка по строкам в список, далее регулярка для разбора строк внешнего уровня, доступ к вложенной строке по индексу +1, далее регулярка. 3) автомат на свиче + дальше однострочная регулярка для вложенных строк

asdpm
()
Последнее исправление: asdpm (всего исправлений: 1)
Ответ на: комментарий от watchcat382

а не получится ли саму команду qmcli из awk вызывать в цикле

# cat test | awk 'match($0, /([-0-9.]+) dBm?/, m) {system("echo execute: qmcli " m[1])}'
execute: qmcli -73
execute: qmcli -73
execute: qmcli -2.5
execute: qmcli -106
execute: qmcli 9.0
execute: qmcli -8
execute: qmcli 7.0
execute: qmcli -101
iron ★★★★★
()

У меня крутится похожая штука для бесперебойника. Считая бинарник, общающийся с хардварью чёрным ящиком, я сделал защиту от изменения порядка выхлопа строк. Ниже файлы, может пригодится. Запрос запускается в цикле, а sh и awk в единственном экземпляре до ребута.

cat /usr/local/bin/upslog.sh

#!/bin/sh

# раньше оно работало в кроне вот так
# /usr/bin/upsc ippon-spp-2000@localhost 2> >( /usr/bin/grep -v 'Init SSL without certificate database' >&2) | /usr/local/bin/upslog.awk >> /var/log/ups_state.log
[ -s /var/log/ups_state.log ] || FIRSTRUN="-vFIRSTRUN=1"
while :; do
    sleep 60 &
    /usr/bin/upsc ippon-spp-2000@localhost
    wait
done |
    /usr/local/bin/upslog.awk $FIRSTRUN >> /var/log/ups_state.log

cat /usr/local/bin/upslog.awk

#!/bin/gawk -f
BEGIN {
        FS=":\\s+"
        split("\
TIME\n\
battery.charge\n\
battery.runtime\n\
battery.voltage\n\
battery.voltage.high\n\
battery.voltage.low\n\
input.frequency\n\
input.voltage\n\
input.voltage.fault\n\
output.voltage\n\
ups.load\n\
ups.status\
", datakeys, "\n")

        for(i in datakeys)
                data[datakeys[i]]=""
        if (FIRSTRUN)
        {
                printf("SEP=;\n");
                for(i in datakeys)
                        printf("%s;",datakeys[i])
                printf("\n")
        }
}
$1 in data {
        data[$1]=$2
}

/ups.vendorid/ {
        data["TIME"]=strftime("%FT%T%z")
        for(i in datakeys)
                printf("%s;",data[datakeys[i]])
        printf("\n")
        fflush()
}

cat /usr/local/lib/systemd/system/upslog.service

[Unit]
Description=Log ups status
After=nut-server.service
Requires=nut-server.service

[Service]
Type=simple
ExecStart=/usr/local/bin/upslog.sh

[Install]
WantedBy=default.target

Можно ещё запилить конфиг для logrotate, но мне было лень.

PS бонус - график

cat ~/bin/ups_state.gnuplot

#!/usr/bin/gnuplot

reset
set title "ИБП"
set time
set timefmt "%Y-%m-%dT%H:%M:%S+0300"
TZ=+3*60*60
set grid
set key autotitle columnhead 
set datafile separator ";"
set term wxt size 1200,800
set xdata time
set format x "%y-%m-%d %H:%M"
set xtics rotate by 30 right autofreq
set ylabel "вольт"
set y2label "минут"
set y2tics
set style data lines
SEESECS=36000
SEEFWD=1200
bind all "Close" "unset output ; exit gnuplot"
#while (1) {
#    if (exists("GPVAL_DATA_X_MAX")) {
#        if (GPVAL_DATA_X_MAX > SEESECS) {
#            set xrange[GPVAL_DATA_X_MAX-SEESECS:GPVAL_DATA_X_MAX+SEEFWD];
#        }
#    }
    set y2range[0:100]
    set xrange[time(0)+TZ-SEESECS:time(0)+TZ+SEEFWD];
    plot "/var/log/ups_state.log" \
           skip 1 using 1:8,\
        "" skip 1 using 1:10,\
        "" skip 1 using 1:11,\
        "" skip 1 using 1:4,\
        "" skip 1 using 1:($3/60) axis x1y2
    #pause 30;
    
#}
pause -1
legolegs ★★★★★
()
Ответ на: комментарий от gagarin0

расскажите полностью условия задачи

В первом посте всё полностью и написано - преобразовывать весьма извращенный вывод команды qmicli в строку числе,разделенных запятыми для записи в csv-файл. Можно конечно написать цикл на баше,который будет раз в несколько секунд перезапускать qmicli … | awk Но если команду для получения данных можно запускать из самого awk то почему бы так не сделать?

Другой вариант - запускать qmicli … | awk из crontab,но это тоже перезапуск awk каждый раз

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

тоже перезапуск awk каждый раз

это дешево по накладным расходам

Но если команду для получения данных можно запускать из самого awk

а этот антипаттерн unixway, так делать не надо

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

Тем, что нечитабельно.

Согласен,такую команду для sed не то что написать,ее понять невозможно. Особенно учитывая что обычно sed работает со строками по одной,то есть без LF внутри. Я например вообще не знал что он может обрабатывать по несколько строк сразу.

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

Потому что YAML

Вот именно - один выпендрился,причем в документации нет даже намека на слово YAML,а теперь все остальные должны писать извращенные скрипты и команды чтобы это разбирать :(

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

Я тоже думал в сторону Питона. Так как qmicli вызывает функции из libqmi то по всей видимости можно вызывать их и из Питона, и форматировать полученные данные как удобно. Но я не настолько знаток Питона чтобы сходу разобраться как дергать из него сишную библиотеку. Поэтому решил ограничиться разбором вывода qmicli. Но ничего умнее вот такого на баше родить не смог:

output=$(qmicli -d /dev/cdc-wdm0 --nas-get-signal-strength) 
rssi=$(echo "$output" | awk -F"'" '/RSSI:/ {getline; print $4}' | tr -d ' ')

Это конечно можно допилить до работоспособного состояния и использовать,но не тогда, когда предполагается собирать данные раз в несколько секунд. Потому к общественности и обратился.

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

Предлагается вызывать qmicli отдельно для получения каждого параметра что ли? Не,это еще хуже чем один перезапуск awk для получения сразу всех параметров.

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

тоже перезапуск awk каждый раз

это дешево по накладным расходам

Но не тогда, когда раз в несколько секунд. Делать надо красиво и оптимально. Вон коллега выше сделал для UPS. Буду пожалуй в его конструкции разбираться - у него awk не перезапускается каждый раз. Всего-то надо адаптировать предложенный выше скрипт awk для разбора qmicli для такого варианта его использования.

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

У меня не установлен qmicli, но интернет говорит, что есть --nas-get-signal-info

# qmicli -d /dev/cdc-wdm0 --nas-get-signal-info
[/dev/cdc-wdm0] Successfully got signal info
LTE:
	RSSI: '-74 dBm'
	RSRQ: '-13 dB'
	RSRP: '-101 dBm'
	SNR: '2.4 dB'

Более «разобрчиво»?

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

Более «разобрчиво»?

Так в том-то и дело что в этом случае сделали нормальный формат вывода,но забыли вывести параметры ECIO,IO,SINR. А там где они выводятся - формат вывода сделали дико неудобным.

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

антипаттерн unixway, так делать не надо

антипаттерн - это каждые несколько секунд перезапускать целую связку достаточно толстых программ вместо одной. Вот представленный коллегой мониторинг для UPS сделан правильно, без лишних перезапусков. Хотя и используется bash+awk но они оба запущены всё время пока скрипт работает.

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

Что мешает для удобства использовать парсер yaml для perl, python, ruby и тп.

И вообще, если формат не определен, то парсить вывод можно только (не)искусственным интеллектом.

ECIO,IO,SINR

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

anonymous
()

Не рекомендую действовать методом «найти все числа». Потом формат вывода команды где-нить изменится, числа станут в другом порядке или другом наборе и у тебя начнут записываться ложные данные. Я бы составил строгий шаблон этого вывода и парсил через него.

То есть:

1) пропускаем «Current:\n'tNetwork 'lte': '»

2) парсим число float A1

3) пропускаем " dBm'\nRRSI:\n\tNetwork 'lte': '"

и т.д.

При несовпадении хотя бы одного символа в пропускаемом тексте или при ошибке парсинга числа (т.е. мы ждём что там число а там что-то другое) - сразу сигнализировать об ошибке - оповещать тебя чтобы ты смотрел что случилось и патчил парсер.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от anonymous

использовать парсер yaml

Как-то очень уж из пушки по воробьям получается. Вместо решения простой задачи разбора нескольких строчек вывода - изучение этих парсеров и их особенностей. Вон выше решение для UPS сделано я считаю идеально для такой задачи сбора данных. Так что адаптирую под него скрипт на awk предложенный в начале обсуждения и буду пользоваться.

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

Так ведь придется искать эти формулы и самому вычислять. А тут уже готовые есть,вычисляемые по правильным формулам.

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

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

$cat ~/example.yml  | yq '.. | select(tag == "!!str") | split(" ") | omit([1])' | yq '. as $item ireduce([]; $item + .) | omit([0])' -o csv

-73,-2.5,-106,9.0,-8,7.0,-101

Только первую строку из выхлопа qmicli убрать, и будет ямл.

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)
Ответ на: комментарий от anonymous

Попробовал в качестве эксперимента. Оказалось что там не просто вывод неудобного вида,так еще и табы вместо пробелов вставлены:( Перед словом «Network».

yaml.scanner.ScannerError: while scanning for the next token found character ‘\t’ that cannot start any token

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

А вот если в файлике куда вывод qmicli я записал поменять табы на пробелы - таки действительно работает.

{'Current': {"Network 'lte'": '-74 dBm'}, 'RSSI': {"Network 'lte'": '-74 dBm'}, 'ECIO': {"Network 'lte'": '-2.5 dBm'}, 'IO': '-106 dBm', 'SINR (8)': '9.0 dB', 'RSRQ': {"Network 'lte'": '-7 dB'}, 'SNR': {"Network 'lte'": '15.0 dB'}, 'RSRP': {"Network 'lte'": '-98 dBm'}}

Но не совсем так как надо бы. Значениями параметров получаются не числа, а строки с размерностью типа ‘15.0 dB’. Так что это надо дальше обрабатывать. Понятно что сделать можно но опять руками.

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

Тем, что нечитабельно.

Я нашел способ прочитать эту команду для sed. Идем вот сюда:

https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1

и просим искусственный интеллект:

" объясни что делает вот эта команда sed -rn «/dBm?‘/ {s/.*’(-?[0-9.]+).*/\1/g; H}; ${g; s/\n/,/g; s/^,//; p}» "

Как ни странно - объяснил внятно, подробно и бесплатно. Мне понравилось.

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

И чем конвейер из трех вызовов sed | yq | yq лечше чем один вызов хоть того же sed хотя и с зубодробительной командой? Если уж всё равно конвейер каждый несколько секунд запускать то лучше бы ему быть как можно короче.

watchcat382 ★★
() автор топика
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария