Условно grep значение из JSON-подобных пар ключ-значение в Bash

#json #bash #grep

#json #bash #grep

Вопрос:

Я использую API, который возвращает данные JSON. В данных обычно не хватает нескольких символов в конце, поэтому технически они «похожи на JSON», поскольку они слегка искажены.

Я могу извлечь из него интересующую область, используя grep это в моем скрипте Bash:

 grep -Po '"username": *K"[^"]*"' jsonraw > jsonclean
  

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

Например, я хочу, чтобы оно возвращало username значение только в том случае, если activity_count поле есть >=1 , иначе просто пропустите запись. Некоторый псевдокод для представления этого может выглядеть так:

 if  '"activity_count":' >=1 grep -Po '"username": *K"[^"]*"' jsonraw > jsonclean
  

Я понимаю, что jq это может быть более простой альтернативой, но я предпочитаю придерживаться из grep -за искаженного конца данных JSON и других причин.

Пример данных:

 [
{"id":"37da1db11b6b4977902baa286f88bf05","activity_count":0,"blocked":false,"coverPhoto":"cb861013bdcc4e5f9e2a93394a7b4309","followed":true,"human":true,"integration":false,"joined":"20190602125229","muted":false,"name":"AV8R","rss":false,"private":false,"profilePhoto":"511d4625df2442fc9b02ab4279c28f09","subscribed":false,"username":"APALMER66","verified":false,"verifiedComments":false,"badges":[0],"score":"1.4k","interactions":259},{"id":"525f9e87bb2d4f4184d12037050afc8d","activity_count":2,"blocked":false,"coverPhoto":"b0bbb4dec22f40d6a347dfb666ff0158","followed":true,"human":true,"integration":false,"joined":"20200627154134","muted":false,"name":"DeziRay","rss":false,"private":false,"profilePhoto":"86627047425844fcbf921e53fc71d106","subscribed":false,"username":"Deziray","verified":false,"verifiedComments":false,"badges":[0],"score":"4.7k","interactions":259},
  

Ожидаемый результат:

 Deziray
  

Комментарии:

1. FWIW, ответ jq, который не заботится о искаженном конце документа, возможен, если использовать режим потоковой передачи.

2. …с другой стороны, вы в порядке с Python? Для Python также доступны парсеры JSON на основе потоков, такие как pypi.org/project/jsonslicer , и было бы довольно просто создать функцию оболочки, которая обертывает короткий скрипт Python, используя один из них.

3. Я добавил образцы данных. Спасибо @CharlesDuffy за информацию о jq том, что я могу рассмотреть это, если нет хорошего grep ответа, но было несколько других причин, по которым я хотел придерживаться grep , например, он уже присутствует в любой системе Linux, поэтому код будет работать без необходимости его установки. То же самое для Python, хотя да, мне лично нравится Python. Я просто пытаюсь сохранить скрипт свободным от зависимостей, если смогу.

4. Ах — ладно, это немного сложнее, чем я думал тогда, спасибо за разъяснение.

5. …добавлен ответ jq для начала, попытаюсь узнать об ответе Python, когда у меня появится такая возможность.

Ответ №1:

Во-первых (потому что это проще), jq ответ:

 jq -nr --stream '
fromstream(1|truncate_stream(inputs))
| select(.activity_count >= 1)
| .username
' <test.json
  

Поскольку это работает в потоковом режиме, оно способно обрабатывать даже усеченные документы.

Комментарии:

1. Это здорово, спасибо. Если у кого-нибудь есть grep ответ, который все равно был бы полезен (и мне, возможно, придется переместить зеленую галочку — но пока это работает, еще раз спасибо).

Ответ №2:

Как собственная реализация Python, основанная на современной среде выполнения Python 3.x:

 #!/usr/bin/env python3
import json, sys

def found_obj_cb(item):
    if item.get('activity_count', 0) >= 1 and 'username' in item:
        print(item['username'])
    return item

try:
    json.load(sys.stdin, object_hook=found_obj_cb)
except json.JSONDecodeError:
    pass
  

… используется из оболочки как:

 #!/usr/bin/env bash

json_parse_py=$(cat <<'EOF'
import json, sys

def found_obj_cb(item):
    if item.get('activity_count', 0) >= 1 and 'username' in item:
        print(item['username'])
    return item

try:
    json.load(sys.stdin, object_hook=found_obj_cb)
except json.JSONDecodeError:
    pass
EOF
)

# define a shell function to wrap the Python code
json_parse() { python3 -c "$json_parse_py" "$@"; }

# actually call it, with test.json on stdin
json_parse <test.python