Ponownie zindeksuj z poleceniem sed, aby przeanalizować tekst JSON

15

Mam ten tekst JSON:

{
    "buildStatus" : {
        "status" : "ERROR",
        "conditions" : [{
                "status" : "OK",
                "metricKey" : "bugs"
            }, {
                "status" : "ERROR",
                "metricKey" : "test_success_density"
            }, {
                "status" : "OK",
                "metricKey" : "vulnerabilities"
            }
        ],
        "periods" : []
    }
}

Chcę wyodrębnić ogólny status buildStatus, tzn. Oczekiwany wynik to „ERROR”

"buildStatus" : {
    "status" : "ERROR",
    ....
}

Próbowałem poniższego wyrażenia sed, ale nie działa, zwraca OK:

status= sed -E 's/.*\"buildStatus\":.*\"status\":\"([^\"]*)\",.*/\1/' jsonfile

Co ja robię źle?

użytkownik1876040
źródło

Odpowiedzi:

16

Nie analizuj złożonych zagnieżdżonych struktur danych, takich jak JSON lub XML, z wyrażeniami regularnymi, użyj właściwego analizatora składni JSON, takiego jak jshon.

Najpierw musisz go zainstalować:

sudo apt-get install jshon

Następnie musisz podać dane JSON do analizy przez standardowe wejście, aby można było przekierować dane wyjściowe innego polecenia za pomocą potoku ( |) lub przekierować do niego plik ( < filename).

Argumenty potrzebne do wyodrębnienia potrzebnych danych wyglądają tak:

jshon -e "buildStatus" -e "status" -u
  • -e "buildStatus" wybiera element z indeksem „buildStatus” ze słownika najwyższego poziomu.
  • -e "status" wybiera element o indeksie „status” ze słownika drugiego poziomu wybranego powyżej.
  • -u konwertuje wybrane dane z JSON na zwykłe dane (tj. tutaj usuwa cudzysłowy wokół łańcucha)

Tak więc polecenie, które uruchamiasz, w zależności od tego, skąd bierzesz dane, wygląda następująco:

jshon -e "buildStatus" -e "status" -u < YOUR_INPUT_FILE
YOUR_JSON_PRODUCING_COMMAND | jshon -e "buildStatus" -e "status" -u

Aby dowiedzieć się więcej jshon, możesz przeczytać jego stronę dostępną online tutaj lub po prostu pisząc man jshon.

Bajt Dowódca
źródło
6
Są też jq:jq -r .buildStatus.status
muru
@HTNW Nigdy nie podobała mi się ta odpowiedź, ponieważ „pojedynczy otwarty tag XML” (o co zadaje pytanie) to zwykły język (i w zasadzie można zbudować pełny parser XML, używając wyrażeń regularnych w celu dopasowania tagów, komentarzy, cdata sekcje i używanie prostego stosu do obsługi zagnieżdżonego kontekstu). Jednak najbardziej „interesującym” językiem regularnym w JSON jest dosłowny ciąg znaków.
Random832
10

Praca dla jq:

jq -r '.["buildStatus"]["status"]' file.json

Można skrócić do:

jq -r '.buildStatus.status' file.json

-r( --raw-output) wyświetla ciąg bez jsonformatowania ciągu, tzn. bez cudzysłowów.

Przykład:

% cat file.json                   
{
    "buildStatus" : {
        "status" : "ERROR",
        "conditions" : [{
                "status" : "OK",
                "metricKey" : "bugs"
            }, {
                "status" : "ERROR",
                "metricKey" : "test_success_density"
            }, {
                "status" : "OK",
                "metricKey" : "vulnerabilities"
            }
        ],
        "periods" : []
    }
}

% jq -r '.["buildStatus"]["status"]' file.json
ERROR

% jq -r '.buildStatus.status' file.json       
ERROR

Jeśli jeszcze nie został zainstalowany, zainstaluj go przez (dostępny w repozytorium Universe):

sudo apt-get install jq 
heemayl
źródło
8

Jak już wspomniano, parsowanie złożonych danych strukturalnych jest lepsze przy odpowiednim API. Python ma jsondo tego moduł, którego osobiście używam dość często w moich skryptach i dość łatwo jest wyodrębnić pożądane pola, tak jak chcesz:

$ python -c 'import sys,json;print json.load(sys.stdin)["buildStatus"]["status"]' <  input.txt
ERROR

To, co się tutaj dzieje, polega na tym, że przekierowujemy plik wejściowy na standardowe wejście Pythona i czytamy to za pomocą json.load(). Staje się słownikiem Pythona z kluczem „buildStatus” i zawiera inny słownik Pythona z kluczem „status”. Dlatego po prostu wypisujemy wartość klucza w słowniku, który jest przechowywany w innym słowniku. Dość proste.

Oprócz prostoty, kolejną zaletą jest to, że Python i ten interfejs API są wstępnie zainstalowane i domyślnie dostarczane z Ubuntu.

Sergiy Kolodyazhnyy
źródło
6

Możesz to zrobić w rzeczywistości sed, ale zdecydowanie zachęcam do korzystania z bardziej wyrafinowanego języka, w którym są napisane narzędzia do obsługi danych JSON. Możesz na przykład wypróbować Perla lub Pythona.

Teraz, w twoim prostym przykładzie, wszystko czego chcesz to pierwsze wystąpienie "status", więc możesz zrobić:

$ sed -nE '/status/{s/.*:\s*"(.*)",/\1/p;q}' file.json 
ERROR

Sztuczka polega na tym, -naby uniknąć drukowania, a jeśli linia pasuje status( /status/), usuwasz wszystko oprócz wybranej częścis/.*:\s*"(.*)",/\1/p żądanej , rintujesz linię i qużywasz.


Osobiście uważam, że to równoważne polecenie grep jest znacznie prostsze:

$ grep -m1 -oP '"status"\s*:\s*"\K[^"]+' file.json 
ERROR

Albo ten:

$ perl -ne 'if(s/.*"status"\s*:\s*"([^"]+).*/$1/){print;exit}' file.json 
ERROR

Poważnie jednak, jeśli planujesz analizować pliki JSON, nie próbuj tego robić ręcznie. Użyj właściwego parsera JSON.

terdon
źródło
lub ten:grep -m 1 status file.json | tr -cd '[[:alnum:]]:' | cut -f2 -d':'
slowko
1
@ user1876040 nie ma za co. Pamiętaj, aby zaakceptować jedną z odpowiedzi (polecam ByteCommander , jego jest lepszym rozwiązaniem), aby pytanie można było oznaczyć jako odpowiedź).
terdon
6

Nie twierdzę, że należy użyć sed(chyba ktoś downvoted mi tylko nie pisać obowiązkowe zastrzeżenie), ale jeśli trzeba szukać czegoś na kolejnej linii do buildStatusjak to wydaje się być trudny w swoim własnym próbie, trzeba powiedzieć sed, aby przeczytać następny wiersz z Npoleceniem

$ sed -rn '/buildStatus/N;s/.*buildStatus.*\n.*: "(.*)",/\1/p' file
ERROR

Uwagi:

  • -n nie drukuj niczego, dopóki nie poprosimy o to
  • -rużyj ERE (tak samo jak -E)
  • /buildStatus/N znajdź ten wzór i przeczytaj także następny wiersz
  • s/old/new/wymienić oldznew
  • .* dowolna liczba dowolnych znaków w linii
  • \n Nowa linia
  • : "(.*)",zapisz wszystkie znaki występujące pomiędzy : "a",
  • \1 odniesienie do zapisanego wzoru
  • p wydrukuj część, nad którą pracowaliśmy
Zanna
źródło
0

Istnieje typowe wyjaśnienie, dlaczego sed i podobne narzędzia do przetwarzania strumienia tekstu nie są dobrze przygotowane do analizowania danych strukturalnych, takich jak JSON i XML. Nie mam tego pod ręką, ale jest na zewnątrz i uważam, że chodzi o to, że wyrażenia potrzebne we wszystkich, ale prawdopodobnie w najmniejszej liczbie sytuacji, szybko stają się bardzo złożone, podczas gdy alternatywne narzędzia zbudowane specjalnie do analizowania struktury są bardziej elegancki, czytelny i wydajny przy tym samym parsowaniu.

Jak muru włożył w komentarzu , jqpowinny być odpowiednim narzędziem do pracy. Mogę też ręczyć za to, że osobiście jestem bardzo podekscytowany, widząc, że zastępuje go kilka razy, gdy próbowałem parsować te same dane, nie osiągając prawie żadnego sukcesu lub obciążonego. Zawiera nawet wiele informacji o możliwościach formatowania i kontrolowania wyników w inny sposób. Wolę to z jsontooljakiegoś powodu, którego obecnie zapominam.

Bajt dowódca wydaje polecić jshonw innej odpowiedzi . Nie korzystałem z tego narzędzia, ale przypomina mi ono o xmlstarletjego składni, a także o dostosowywanej prezentacji danych wyjściowych.

Pysis
źródło
Prawdopodobnie mówisz o stackoverflow.com/a/1732454/2072269
muru
3
Zastanów się nad poprawieniem swojej odpowiedzi, pokazując przykład, w jaki sposób jsontoolmożna wykorzystać ją w konkretnym przypadku OP
Sergiy Kolodyazhnyy
Lol @muru, zgadza się, to jest jeden z postów, które próbują zniechęcić do korzystania z parsowania XML / JSON z Regex! Bardziej jqpolecałem Muru i heemaylowi, że mają już przykłady, i po prostu podałem uzasadnienie: askubuntu.com/a/863948/230721
Pysis
0

Po prostu kolejne narzędzie Json o nazwie json ( https://github.com/trentm/json )

$ json buildStatus.status < file.json
ERROR

To studium przypadku wprowadza w błąd: wygląda na to, że narzędzia nie działają. Możesz także użyćjson do zmiany plików Json:

$ json -e 'this.buildStatus.status="not error"' < file.json > new.json

lub nawet...

$ json -e 'this.buildStatus.status="no errors"' < file.json | json -e 'this.buildStatus.status
no errors

dokumentacja w: http://trentm.com/json/


jeśli nie jest zainstalowany:

  • zainstaluj węzeł
  • i sudo npm install -g json

źródło