Jak wyodrębnić dane z pliku JSON

13

Mam bin szukający rozwiązania dla mojego pytania, ale nie znalazłem ani lepiej powiedział, że nie dostałem go z tym, co znalazłem. Porozmawiajmy więc o tym, o czym jest mój problem. Korzystam z oprogramowania Smart Home Control na Raspberry Pi i jak się dowiedziałem w ten weekend za pomocą pilota, mogę przechwycić dane z mojego czujnika temperatury zewnętrznej. Wyjście pilight-receive wygląda następująco:

{
        "message": {
                "id": 4095,
                "temperature": 409.5
        },
        "origin": "receiver",
        "protocol": "alecto_wsd17",
        "uuid": "0000-b8-27-eb-0f3db7",
        "repeats": 3
}
{
        "message": {
                "id": 1490,
                "temperature": 25.1,
                "humidity": 40.0,
                "battery": 1
        },
        "origin": "receiver",
        "protocol": "alecto_ws1700",
        "uuid": "0000-b8-27-eb-0f3db7",
        "repeats": 3
}
{
        "message": {
                "id": 2039,
                "temperature": 409.5
        },
        "origin": "receiver",
        "protocol": "alecto_wsd17",
        "uuid": "0000-b8-27-eb-0f3db7",
        "repeats": 4
}

Teraz moje pytanie do ciebie: jak do cholery mogę wyodrębnić temperaturę i wilgotność, z których identyfikator wynosi 1490. I jak poleciłbyś mi to sprawdzać często? Za pomocą zadania cron, które jest uruchamiane co 10 minut, tworzy dane wyjściowe polecenia pilight-receive, wyodrębnia dane wyjściowe i przekazuje je do interfejsu API Smart Home Control.

Ktoś ma pomysł - wielkie dzięki

Raul Garcia Sanchez
źródło
3
Format wydaje się być JSON . Istnieje wiele sposobów parsowania JSON. To zależy od tego, z czym czujesz się komfortowo. Pyton? JavaScript? Coś innego?
mur
Znam trochę Pythona i trochę JavaScript, znam głównie C ++ i C #. Ale po zobaczeniu wszystkich poleceń awk i sed pomyślałem, że to musi być jakieś łatwe polecenie xD
Raul Garcia Sanchez
1
Nie jest to trudne awki sedpod warunkiem, że wyjście JSON zachowuje formatowanie pokazane tutaj, czego nie musi - białe znaki nie mają znaczenia dla JSON. Na przykład to awkpolecenie: awk '/temperature|humidity/ {print $2}'jest zamknięte.
mur
4
z ksh93parsowaniem json jest wbudowany w read.
mikeserv
1
sprawdź wheezy-backports. może tam być, oszczędzając ci aktualizację do jessie (chyba że i tak planujesz aktualizację). Aha! jest przeniesiony do świszczącego oddechu. packages.debian.org/wheezy-backports/jq
cas

Odpowiedzi:

23

Możesz użyć jqdo przetwarzania plików Json w powłoce.

Na przykład zapisałem twój przykładowy plik json jako, raul.jsona następnie uruchomiłem:

$ jq .message.temperature raul.json 
409.5
25.1
409.5
$ jq .message.humidity raul.json 
null
40
null

jq jest dostępny w pakiecie dla większości dystrybucji Linuksa.

Prawdopodobnie jest na to sposób jqsam w sobie, ale najprostszym sposobem na uzyskanie obu pożądanych wartości w jednym wierszu jest użycie xargs. Na przykład:

$ jq 'select(.message.id == 1490) | .message.temperature, .message.humidity' raul.json | xargs
25.1 40

lub, jeśli chcesz zapętlić każdą .message.idinstancję, możemy dodać .message.iddo wyniku i użyć, xargs -n 3ponieważ wiemy, że będą trzy pola (identyfikator, temperatura, wilgotność):

jq '.message.id, .message.temperature, .message.humidity' raul.json | xargs -n 3
4095 409.5 null
1490 25.1 40
2039 409.5 null

Następnie możesz przetworzyć dane wyjściowe za pomocą awk lub cokolwiek innego.


Wreszcie zarówno Python, jak i Perl mają doskonałe biblioteki do analizowania i manipulowania danymi JSON. Podobnie jak kilka innych języków, w tym php i java.

cas
źródło
2
konkretniejq 'select(.message.id == 1490) | .message.temperature, .message.humidity' raul.json
glenn jackman
1
lub, bash,{ read temp; read hum; } < <(jq ...)
glenn jackman
1
Zobacz moją odpowiedź, która po prostu wykorzystuje grep. Może nie działać w przypadku niektórych konkretnych wersji grep, ale jest prostszy niż jqw tym scenariuszu, chociaż jqzostał zaprojektowany specjalnie do analizowania JSON. Zrobiłem dać jqodebrać upvote jednak niezależnie. Jest to rzeczywiście narzędzie do pracy, ale czasami można po prostu usunąć zszywki palcami, zamiast szukać rozszywacza.
rubynorails,
2
json nie może być niezawodnie parsowany z wyrażeniami regularnymi bardziej niż xml lub html. a większość danych JSON (np. pobieranych przez interfejs WWW) nie jest ładnie sformatowana z dodatkowymi liniami i wcięciami. do niezawodnego parsowania jsona potrzebujesz parsera json. jqjest jednym z takich skryptów powłoki. inne języki mają biblioteki parsujące json.
cas
1
wszystko można rzetelnie przeanalizować za pomocą wyrażeń regularnych. to zależy tylko od tego, ile używasz. jak myślisz, jqjak to robi?
mikeserv
0

jqjest zdecydowanie najbardziej eleganckim rozwiązaniem. Z awktobą możesz pisać

awk -v id=1490 '
    $1 == "\"id\":" && $2 == id"," {matched = 1}
    $1 == "}," {matched = 0}
    matched && $1 ~ /temperature|humidity/ {sub(/,/,"", $2); print $2}
' file
Glenn Jackman
źródło
0

Dla tych, którzy nie rozumieją zaawansowanego awktak dobrze, jak chcieliby (na przykład ludzie tacy jak ja) i nie mają jqwcześniej zainstalowanego systemu, łatwym rozwiązaniem byłoby połączenie kilku natywnych poleceń razem:

grep -A2 '"id": 1490,' stats.json | sed '/1490/d;s/"//g;s/,//;s/\s*//'

Jeśli próbujesz tylko uzyskać wartości, łatwiej jest po prostu użyć grepzamiast awklub sed:

grep -A2 '"id": 1490,' stats.json | grep -o "[0-9]*\.[0-9]*"

Wyjaśniając, wydaje mi się to najprostszym sposobem.

  • grep -A2Chwyta linię szukasz w JSON wraz z następującymi 2 linie, które zawierają temperatury i wilgotności.
  • Rura grep -opo prostu drukuje tylko cyfry oddzielone znakiem .(co nigdy nie nastąpi w pierwszym 1490wierszu, więc pozostały Ci dwie wartości - temperatura i wilgotność. Bardzo proste. jqMoim zdaniem nawet prostsze niż użycie .
rubinorails
źródło
0

Moim wybranym narzędziem do przetwarzania JSON w wierszu poleceń jest jq. Jednak jeśli nie masz zainstalowanego jq, możesz zrobić całkiem nieźle z Perlem:

# perl -MJSON -e '$/ = undef; my $data = <>; for my $hash (new JSON->incr_parse($data)) { my $msg = $hash->{message}; print "$msg->{temperature} $msg->{humidity}\n" if $msg->{id} == 1490 }' < data.json
25.1 40
nwk
źródło
0

Twój wynik to zestaw fragmentów JSON, a nie pełny JSON. Jeśli / raz zmienisz układ wyjściowy na integralny JSON, np. W ten sposób (zakładając, że twój wyjściowy jest w file.json):

echo "[ $(cat file.json | sed -E 's/^}$/},/; $d') }]"

wtedy łatwo jest osiągnąć to, co chcesz za pomocą jtcnarzędzia (dostępne na stronie : https://github.com/ldn-softdev/jtc ):

bash $ echo "[ $(cat file.json | sed -E 's/^}$/},/; $d') }]" | jtc -x "[id]:<1490>d [-1]" -y[temperature] -y[humidity] -l
"temperature": 25.1
"humidity": 40.0
bash $ 

w powyższym przykładzie upuść, -ljeśli nie chcesz drukowanych etykiet

Dmitry L.
źródło