как преобразовать более сложный ориентированный на человека вывод текста в машинно-ориентированный стиль? - PullRequest
1 голос
/ 25 января 2020

Это вопрос о том, как преобразовать «непарсируемый» вывод в json или что-то легко потребляемое как json. Это "немного" немного позади тривиальных вещей, поэтому я хотел бы знать, как вы решаете эти вещи в принципе, речь идет не только об этом конкретном примере c. Но пример:

У нас есть эта команда, которая показывает данные об аудиовходах:

pacmd list-sink-inputs

она печатает что-то вроде этого:

2 sink input(s) available.
    index: 144
    driver: <protocol-native.c>
    flags: 
    state: RUNNING
    sink: 4 <alsa_output.pci-0000_05_00.0.analog-stereo>
    volume: front-left: 15728 /  24% / -37.19 dB,   front-right: 15728 /  24% / -37.19 dB
            balance 0.00
    muted: no
    current latency: 70.48 ms
    requested latency: 210.00 ms
    sample spec: float32le 2ch 44100Hz
    channel map: front-left,front-right
                 Stereo
    resample method: copy
    module: 13
    client: 245 <MPlayer>
    properties:
        media.name = "UNREAL! Tetris Theme on Violin and Guitar-TnDIRr9C83w.webm"
        application.name = "MPlayer"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "32"
        application.process.id = "1543"
        application.process.user = "mmucha"
        application.process.host = "vbDesktop"
        application.process.binary = "mplayer"
        application.language = "C"
        window.x11.display = ":0"
        application.process.machine_id = "720184179caa46f0a3ce25156642f7a0"
        application.process.session_id = "2"
        module-stream-restore.id = "sink-input-by-application-name:MPlayer"
    index: 145
    driver: <protocol-native.c>
    flags: 
    state: RUNNING
    sink: 4 <alsa_output.pci-0000_05_00.0.analog-stereo>
    volume: front-left: 24903 /  38% / -25.21 dB,   front-right: 24903 /  38% / -25.21 dB
            balance 0.00
    muted: no
    current latency: 70.50 ms
    requested latency: 210.00 ms
    sample spec: float32le 2ch 48000Hz
    channel map: front-left,front-right
                 Stereo
    resample method: speex-float-1
    module: 13
    client: 251 <MPlayer>
    properties:
        media.name = "Trombone Shorty At Age 13 - 2nd Line-k9YUi3UhEPQ.webm"
        application.name = "MPlayer"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "32"
        application.process.id = "2831"
        application.process.user = "mmucha"
        application.process.host = "vbDesktop"
        application.process.binary = "mplayer"
        application.language = "C"
        window.x11.display = ":0"
        application.process.machine_id = "720184179caa46f0a3ce25156642f7a0"
        application.process.session_id = "2"
        module-stream-restore.id = "sink-input-by-application-name:MPlayer"

очень приятно. Но мы не хотим показывать пользователю все это, мы просто хотим показать индекс (id ввода), application.process.id, application.name и media.name в некотором приемлемом формате. Было бы здорово разобрать его как-то до json, но даже если я каким-то образом предварительно обработаю его, jq выходит далеко за пределы моих возможностей и довольно сложен. Я пробовал несколько подходов, используя jq, с регулярным выражением или без него, но я не смог его завершить. И я думаю, мы не можем полагаться на порядок или наличие всех полей.

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

Неправильное решение:

cat exampleOf2Inputs | 
grep -e "index: \|application.process.id = \|application.name = \|media.name = " | 
sed "s/^[ \t]*//;s/^\([^=]*\) = /\1: /" | 
tr "\n" ";" | 
sed "s/$/\n/;s/index:/\nindex:/g" | 
tail -n +2 | 
while read A; do 
index=$(echo $A|sed "s/^index: \([0-9]*\).*/\1/");
pid=$(echo $A|sed 's/^.*application\.process\.id: \"\([0-9]*\)\".*$/\1/'); 
appname=$(echo $A|sed 's/^.*application\.name: \"\([^;]*\)\".*$/\1/'); 
medianame=$(echo $A|sed 's/^.*media\.name: \"\([^;]*\)\".*$/\"\1\"/'); 

echo "pid=$pid index=$index appname=$appname medianame=$medianame"; 
done

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

Здесь вывод:

pid=1543 index=144 appname=MPlayer medianame="UNREAL! Tetris Theme on Violin and Guitar-TnDIRr9C83w.webm"
pid=2831 index=145 appname=MPlayer medianame="Trombone Shorty At Age 13 - 2nd Line-k9YUi3UhEPQ.webm"

, который легко конвертируется в любой формат, но вопрос был о json, поэтому:

[
  {
    "pid": 1543,
    "index": 144,
    "appname": "MPlayer",
    "medianame": "UNREAL! Tetris Theme on Violin and Guitar-TnDIRr9C83w.webm"
  },
  {
    "pid": 2831,
    "index": 145,
    "appname": "MPlayer",
    "medianame": "Trombone Shorty At Age 13 - 2nd Line-k9YUi3UhEPQ.webm"
  }
]

Теперь я хотел бы посмотреть, пожалуйста, как все это делается правильно.

Ответы [ 3 ]

2 голосов
/ 26 января 2020

Если ввод такой же разумный, как показано в вопросе Q, должен быть возможен следующий подход, использующий только jq.

Предполагается вызов по следующим строкам:

jq -nR -f parse.jq input.txt
def parse:
  def interpret:
    if . == null then .
    elif startswith("\"") and endswith("\"")
    then  .[1:-1]
    else tonumber? // .
    end;
  (capture( "(?<key>[^\t:= ]*)(: | = )(?<value>.*)" ) // null)
  | if . then .value = (.value | interpret) else . end
;

# Construct one object for each "segment"  
def construct(s): 
  [ foreach (s, 0) as $kv (null;
      if $kv == 0 or $kv.index
      then .complete = .accumulator | .accumulator = $kv
      else .complete = null | .accumulator += $kv
      end;
      .complete // empty ) ]
;


construct(inputs | parse | select(.) | {(.key):.value})
| map( {pid: .["application.process.id"],
        index,
        appname: .["application.name"],
        medianame: .["media.name"]} )

При вводе примера выходные данные будут:

[
  {
    "pid": "1543",
    "index": 144,
    "appname": "MPlayer",
    "medianame": "UNREAL! Tetris Theme on Violin and Guitar-TnDIRr9C83w.webm"
  },
  {
    "pid": "2831",
    "index": 145,
    "appname": "MPlayer",
    "medianame": "Trombone Shorty At Age 13 - 2nd Line-k9YUi3UhEPQ.webm"
  }
]

Краткое объяснение

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

construct отвечает за группирование строк (представленных в виде потока одноключевых объектов со значением ключа), соответствующих конкретному значению «index». Он создает массив объектов, по одному на каждое значение «индекса».

1 голос
/ 26 января 2020

Я не знаю о "правильно", но вот что я бы сделал:

pacmd list-sink-inputs | awk '
    BEGIN { print "[" }
    function print_record() {
        if (count++) {
            print "  {"
            printf "    %s,\n", print_number("pid")
            printf "    %s,\n", print_number("index")
            printf "    %s,\n", print_string("appname")
            printf "    %s\n",  print_string("medianame")
            print "  },"
        }
        delete record
    }
    function print_number(key) { return sprintf("\"%s\": %d", key, record[key]) }
    function print_string(key) { return sprintf("\"%s\": \"%s\"", key, record[key]) }
    function get_quoted_value() {
        if (match($0, /[^"]+"$/))
            return substr($0, RSTART, RLENGTH-1)
        else
            return "?"
    }
    $1 == "index:" { print_record(); record["index"] = $2 }
    $1 == "application.process.id" { record["pid"]       = get_quoted_value() }
    $1 == "application.name"       { record["appname"]   = get_quoted_value() }
    $1 == "media.name"             { record["medianame"] = get_quoted_value() }
    END { print_record(); print "]" }
' | 
  tac | awk '/},$/ && !seen++ {sub(/,$/,"")} 1' | tac

, где строка tac|awk|tac удаляет запятую с запятой last JSON объект в списке.

[
  {
    "pid": 1543,
    "index": 144,
    "appname": "MPlayer",
    "medianame": "UNREAL! Tetris Theme on Violin and Guitar-TnDIRr9C83w.webm"
  },
  {
    "pid": 2831,
    "index": 145,
    "appname": "MPlayer",
    "medianame": "Trombone Shorty At Age 13 - 2nd Line-k9YUi3UhEPQ.webm"
  }
]
0 голосов
/ 26 января 2020

Вы можете просто передать ваш вывод в:

sed -E '
  s/pid=([0-9]+) index=([0-9]+) appname=([^ ]+) medianame=(.*)/{"pid": \1, "index": \2, "appname": "\3", "medianame": \4},/
  1s/^/[/
  $s/,$/]/
' | jq .
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...