Обработать файл, содержащий коллекцию JSON строк - PullRequest
0 голосов
/ 21 апреля 2020

(Название отредактировано. Исходное название: "json content: распечатывать его части как есть, части канала, чтобы получить удобочитаемые метки времени, выводимые из той же команды ")


У меня есть json похожее содержимое в файле:

{
  "newState": "runnable",
  "setAt": 1587421159359
}
{
  "newState": "running",
  "setAt": 1587421282891
}
{
  "newState": "debug_hold",
  "setAt": 1587422014895
}
{
  "newState": "terminating",
  "setAt": 1587424788577
}
{
  "newState": "failed",
  "setAt": 1587424796544
}

Я могу извлечь 'newState' с помощью cat timestamps.json | jq -r '.newState':

runnable
running
debug_hold
terminating
failed

I можно извлечь метки времени эпохи и отформатировать их в удобочитаемую форму с помощью cat timestamps.json | jq -r '.setAt' | rev | cut -c 4- | rev | perl -pe 's/(\d+)/localtime($1)/e':

Mon Apr 20 18:19:19 2020
Mon Apr 20 18:21:22 2020
Mon Apr 20 18:33:34 2020
Mon Apr 20 19:19:48 2020
Mon Apr 20 19:19:56 2020

Как можно объединить два выхода, чтобы результат стал

runnable Mon Apr 20 18:19:19 2020
running Mon Apr 20 18:21:22 2020
debug_hold Mon Apr 20 18:33:34 2020
terminating Mon Apr 20 19:19:48 2020
failed Mon Apr 20 19:19:56 2020

Я думаю, что я может сделать bash для l oop и ввода массива, но мне было интересно, есть ли у jq что-то, что может передать часть контента (например, время эпохи в этом случае), обработать его, а затем передать значение обратно в jq разбирать вывод.

Ответы [ 4 ]

3 голосов
/ 21 апреля 2020

Если вход является коллекцией (не связанных) действительных JSON строк, которые можно прочитать в {} кусках.

Установите входной разделитель записей ($/) в }, а затем оператор <> каждый раз читает до }

use warnings;
use strict;
use feature 'say';

use JSON qw(decode_json);

my $file = shift // die "Usage: $0 file\n";  #/

open my $fh, '<', $file or die "Can't open $file: $!";

local $/ = '}';  # presumably all this code is in some local scope

while (my $record = <$fh>) { 
    next if not $record =~ /\S/; 

    my $json = decode_json($record); 

    say $json->{newState}, ' ', scalar localtime $json->{setAt}/1000; 
}

Комментарии

  • Это зависит от отображаемого формата ввода, в Особенность в том, что у него нет вложенных объектов. Если есть вложенные {...}, тогда вырежьте весь файл и извлеките JSON строк, используя Text::Balanced или эквивалентный (или, конечно, используйте другой подход)

  • Я бы на самом деле Рекомендуется использовать Cpanel::JSON::XS

  • Когда необходимо изменить глобальные переменные, такие как $/, лучше всего это делать в наименьшей необходимой области и с local . Здесь это не имеет значения, но я предполагаю, что это является частью более крупной программы из того, содержит ли запись какие-либо непробельные символы

  • Временные метки в вашем входе отключены в тысячи раз с секунд с начала эпохи, я полагаю, потому что они также несут миллисекунды. Я просто делю на 1000 для простоты

  • Обратите внимание, что показанные желаемые временные метки могут стать проблемой, если используется летнее время, и если это так, вы хотите извлечь и включить время Зона


Самый простой (и гибкий) способ получить часовой пояс эпохи - использовать POSIX::strftime. Он берет список из localtime и возвращает строку, сгенерированную в соответствии с заданным форматом.

Спецификатор %z создает часовой пояс как смещение UT C, в то время как %Z создает короткое имя (пресловутое и непереносимое). Для получения дополнительной информации см. Справочную страницу strftime вашей системы. Пример

 use POSIX qw(strftime);
 say strftime "%z %Z", localtime;  #--> -0700 PDT

(благодаря ответу ikegami, который подтолкнул меня к добавлению обсуждения часовых поясов)

2 голосов
/ 21 апреля 2020

Вы можете выглядеть примерно так.

cat timestamps.json | jq -r '[.newState, .setAt] | join(" ")'
1 голос
/ 21 апреля 2020

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

use Cpanel::JSON::XS qw( );

my $decoder = Cpanel::JSON::XS->new();
while (<>) {
   $decoder->incr_parse($_);
   while ( my $rec = $decoder->incr_parse() ) {
      say sprintf "%-11s %s",
         $rec->{newState},
         format_ts($rec->{setAt});
   }
}

Полная программа:

#!/usr/bin/perl

use strict;
use warnings;
use feature qw( say );

use utf8;
use open ':std', ':encoding(UTF-8)';

use Cpanel::JSON::XS qw( );
use POSIX            qw( strftime );

sub format_ts {
   my ($ts) = @_;
   my $ms = $ts % 1000;
   my $epoch = ( $ts - $ms ) / 1000;
   my @lt = localtime($epoch);
   return sprintf("%s.%03d %s",
      strftime("%a %b %d %H:%M:%S", @lt),
      $ms,
      strftime("%Y %z", @lt),
   );
}

my $decoder = Cpanel::JSON::XS->new();
while (<>) {
   $decoder->incr_parse($_);
   while ( my $rec = $decoder->incr_parse() ) {
      say sprintf "%-11s %s",
         $rec->{newState},
         format_ts($rec->{setAt});
   }
}

Вывод:

runnable    Mon Apr 20 18:19:19.359 2020 -0400
running     Mon Apr 20 18:21:22.891 2020 -0400
debug_hold  Mon Apr 20 18:33:34.895 2020 -0400
terminating Mon Apr 20 19:19:48.577 2020 -0400
failed      Mon Apr 20 19:19:56.544 2020 -0400

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

0 голосов
/ 21 апреля 2020

Небольшой perl скрипт может с легкостью обрабатывать такие данные

ИСПОЛЬЗОВАНИЕ: метки времени script_name.pl. json

#!/usr/bin/perl

use strict;
use warnings;

my($state,$time);

while(<>) {
    chomp;
    $state = $1 if /"newState": "(.*)"/;
    $time  = $1 if /"setAt": (\d+)/;
    printf "%-12s %s\n", $state, "".localtime($time/1000) if /}/;
}

Альтернативная версия

use strict;
use warnings;

my $data = do { local $/; <> };
my %state = $data =~ /"newState": "(.*?)".*?"setAt": (\d+)/sg;

while(my($s,$t) = each %state) {
    printf "%-12s %s\n", $s, "".localtime($t/1000);
}

Входной файл отметки времени. json

{
  "newState": "runnable",
  "setAt": 1587421159359
}
{
  "newState": "running",
  "setAt": 1587421282891
}
{
  "newState": "debug_hold",
  "setAt": 1587422014895
}
{
  "newState": "terminating",
  "setAt": 1587424788577
}
{
  "newState": "failed",
  "setAt": 1587424796544
}

Вывод

runnable     Mon Apr 20 15:19:19 2020
running      Mon Apr 20 15:21:22 2020
debug_hold   Mon Apr 20 15:33:34 2020
terminating  Mon Apr 20 16:19:48 2020
failed       Mon Apr 20 16:19:56 2020
...