jq: преобразовать заголовок: текстовый файл line1 line2 в поток JSON с отображением в списки строк

Как преобразовать эти списки текстовых строк в json

Текстовые строки:

start filelist:
/download/2017/download_2017.sh
/download/2017/log_download_2017.json
/download/2017/log_download_2017.txt
start wget:
2017-05-15 20:42:00 URL:http://web.site.com/downloads/2017/file_1.zip [1024/1024] -> "file_1.zip" [1]
2017-05-15 20:43:21 URL:http://web.site.com/downloads/2017/file_2.zip [2048/2048] -> "file_2.zip" [1]

Вывод JSON:

{
"start filelist": [
    "download_2017.sh",
    "log_download_2017.txt",
    "log_download_2017.json",
  ],
}
{
"start wget": [
    "2017-05-15 20:43:01 URL:http://web.site.com/downloads/2017/file_1.zip [1024/1024] -> "file_1.zip" [1]",
    "2017-05-15 20:43:21 URL:http://web.site.com/downloads/2017/file_2.zip [2048/2048] -> "file_2.zip" [1]",
  ],
}

Ценим любые варианты и подходы


person Gabe    schedule 15.05.2017    source источник
comment
Запускать по одной копии tee на строку вашего скрипта безумно неэффективно, и, кроме того, это означает, что ни одна программа не может генерировать единый, непротиворечивый, синтаксически допустимый документ JSON.   -  person Charles Duffy    schedule 16.05.2017
comment
@CharlesDuffy спасибо, это приятно знать. Исторически сложилось так, что для относительно простых операций ведения журналов, когда накладные расходы журнала не имели большого значения, я просто перенаправлял вывод в файл журнала txt. Здесь я хотел бы перенаправить вывод в два файла журнала: txt и json. Возможно, в следующей итерации будет задействован третий файл журнала: xml. Пожалуйста, объясните, в чем неэффективность? Накладные расходы на тройник настолько высоки, что их следует избегать? Какие могут быть альтернативные подходы?   -  person Gabe    schedule 16.05.2017
comment
Это буквально в сотни или тысячи раз превышает стоимость производительности echo для настройки конвейера, выполняющего внешние команды. Каждый конвейер состоит из mkfifo()s, fork()s и, если выполняются внешние команды, exec()s. Более того, всякий раз, когда вы запускаете >>file, эта команда открывает файл для вывода до его запуска, а затем сбрасывает и закрывает его, когда он заканчивается, что намного дороже, чем просто открыть файл один раз и оставить его открытым для многократного выполнения команды.   -  person Charles Duffy    schedule 16.05.2017
comment
Кстати, не могли бы вы разделить последующий вопрос о том, как передать поток вывода из нескольких команд в jq, в отдельный вопрос? Если ответ @peak адекватно затрагивает суть проблемы, то это следует принять; и содержание вне его области должно быть где-то еще, чтобы адресоваться.   -  person Charles Duffy    schedule 16.05.2017
comment
Понятно. Еще раз спасибо, почти уверен, что @peak рассмотрел основную проблему вопроса, и вы обратились к реализации сценария оболочки для поддержки этого. Просто тестирование сейчас подтвердит в ближайшее время.   -  person Gabe    schedule 16.05.2017


Ответы (1)


Вот решение только для jq, которое создает действительный JSON в соответствии с вашим примером:

foreach (inputs,null) as $line ({};
   if $line == null then .emit = {(.key): .value}
   elif $line[-1:] == ":"
   then (if .key then {emit: {(.key): .value}} else null end)
        + { key : $line[0:-1] }
   else {key, value: (.value + [$line])}
   end;
   .emit // empty )

Вызов:

jq -n -R -f program.jq input.txt

Обратите особое внимание на параметр -n.

Предостережения

Если ввод не начинается с «ключевой» строки, то вышеуказанная программа jq сообщит об ошибке и завершится. Если требуется большая отказоустойчивость, то может быть интересен следующий вариант:

foreach (inputs,null) as $line ({};
   if $line == null then .emit = {(.key|tostring): .value}
   elif $line[-1:] == ":"
   then (if .key then {emit: {(.key): .value}} else null end)
        + { key : $line[0:-1] }
   else {key, value: (.value + [$line])}
   end;
   .emit // empty )
person peak    schedule 15.05.2017
comment
Вы можете подумать о шебанге (то есть #!/usr/bin/env jq -nRf), чтобы это просто запускалось как ./program input.txt или ./program <input.txt. - person Charles Duffy; 16.05.2017
comment
Спасибо. Это элегантное решение. Использование продолжающейся записи в текстовый файл позволяет циклу обрабатывать текстовый файл для записи в json. Он хорошо работает для первого массива 'start filelist ls -1 pwd/* | tee -a $logfilename.txt | jq -n -R -f json_log_array.jq $logfilename.txt >> $logfilename.json, однако, похоже, возникла проблема со вторым массивом 'start wget' 'wget -nv web.site.com/downloads/2017/file_1.zip 2›&1 | tee -a $logfilename.txt | jq -n -R -f json_log_array.jq $logfilename.txt ›› $logfilename.json', и я получаю странные результаты в файле json. - person Gabe; 16.05.2017
comment
@Gabe, не делайте несколько отдельных перенаправлений. Передайте все ваши команды ввода в одиночный экземпляр скрипта, приведенный в этом ответе. - person Charles Duffy; 16.05.2017
comment
@CharlesDuffy спасибо, понял. Потребовалось небольшое исследование и просмотр, но теперь это имеет смысл. Ваше предложение ниже отлично сработало в сочетании с program.jq - person Gabe; 16.05.2017