it-swarm.com.de

Erstellen Sie JSON mit jq aus durch Pipe getrennten Schlüsseln und Werten in der Bash

Ich versuche, ein Json-Objekt aus einer Zeichenfolge in Bash zu erstellen. Die Zeichenfolge lautet wie folgt. 

CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0

Die Ausgabe stammt vom Docker-Statistikbefehl, und mein Endziel besteht darin, benutzerdefinierte Metriken in aws cloudwatch zu veröffentlichen. Ich möchte diese Zeichenfolge als Json formatieren. 

{
    "CONTAINER":"nginx_container",
    "CPU%":"0.02%", 
    ....
}

Ich habe jq vorher verwendet und es scheint, dass es in diesem Fall gut funktionieren sollte, aber ich konnte noch keine gute Lösung finden. Andere als Hardcoding-Variablennamen und Indizierung mit sed oder awk. Dann erstellen Sie einen Json von Grund auf. Anregungen werden gebeten. Vielen Dank. 

14
michael_65

Voraussetzung

Für alle untenstehenden wird davon ausgegangen, dass sich Ihr Inhalt in einer Shell-Variablen mit dem Namen s befindet:

s='CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0'

Was (moderne jq)

# thanks to @JeffMercado and @chepner for refinements, see comments
jq -Rn '
( input  | split("|") ) as $keys |
( inputs | split("|") ) as $vals |
[[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries
' <<<"$s"

Wie (modern jq)

Dies erfordert sehr neue (wahrscheinlich 1,5?) jq und ist ein dichter Codeblock. Um es aufzubrechen:

  • Durch Verwendung von -n wird jq daran gehindert, stdin selbst zu lesen, und der gesamte Eingabestrom kann von input und inputs gelesen werden, wobei erstere eine einzelne Zeile lesen und letztere alle übrigen Zeilen lesen kann. (-R bewirkt bei roher Eingabe, dass Textzeilen anstelle von JSON-Objekten gelesen werden).
  • Mit [$keys, $vals] | transpose[] erzeugen wir [key, value]-Paare (in Python ausgedrückt: Zippen der beiden Listen).
  • Mit {key:.[0],value:.[1]} machen wir jedes [key, value]-Paar zu einem Objekt der Form {"key": key, "value": value}.
  • Mit from_entries kombinieren wir diese Paare zu Objekten, die diese Schlüssel und Werte enthalten.

Was (Shell-unterstützt)

Dies funktioniert mit einer wesentlich älteren jq als der oben genannten und ist ein einfach anzuwendender Ansatz für Szenarien, in denen eine native -jq-Lösung schwerer zu umgehen ist:

{
   IFS='|' read -r -a keys # read first line into an array of strings

   ## read each subsequent line into an array named "values"
   while IFS='|' read -r -a values; do

    # setup: positional arguments to pass in literal variables, query with code    
    jq_args=( )
    jq_query='.'

    # copy values into the arguments, reference them from the generated code    
    for idx in "${!values[@]}"; do
        [[ ${keys[$idx]} ]] || continue # skip values with no corresponding key
        jq_args+=( --arg "key$idx"   "${keys[$idx]}"   )
        jq_args+=( --arg "value$idx" "${values[$idx]}" )
        jq_query+=" | .[\$key${idx}]=\$value${idx}"
    done

    # run the generated command
    jq "${jq_args[@]}" "$jq_query" <<<'{}'
  done
} <<<"$s"

Wie (Shell-unterstützt)

Der aufgerufene jq Befehl von oben ist ähnlich wie:

jq --arg key0   'CONTAINER' \
   --arg value0 'nginx_container' \
   --arg key1   'CPU%' \
   --arg value1 '0.0.2%' \
   --arg key2   'MEMUSAGE/LIMIT' \
   --arg value2 '25.09MiB/15.26GiB' \
   '. | .[$key0]=$value0 | .[$key1]=$value1 | .[$key2]=$value2' \
   <<<'{}'

... übergeben Sie jeden Key und Wert außerhalb des Bandes (so dass er nicht als JSON, sondern als Literal-Zeichenfolge behandelt wird), und referenzieren Sie sie einzeln.


Ergebnis

Jedes der oben genannten wird emittieren:

{
  "CONTAINER": "nginx_container",
  "CPU%": "0.02%",
  "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
  "MEM%": "0.16%",
  "NETI/O": "0B/0B",
  "BLOCKI/O": "22.09MB/4.096kB",
  "PIDS": "0"
}

Warum

Kurz gesagt: Weil es garantiert ist, gültige JSON als Ausgabe zu generieren .

Betrachten Sie das Folgende als ein Beispiel, das naivere Ansätze bricht:

s='key ending in a backslash\
value "with quotes"'

Sicher, dies sind unerwartete Szenarien, aber jq weiß, wie sie damit umgehen sollen:

{
  "key ending in a backslash\\": "value \"with quotes\""
}

... während eine Implementierung, die JSON-Strings nicht verstand, leicht zu einem Ergebnis führen könnte:

{
  "key ending in a backslash\": "value "with quotes""
}
32
Charles Duffy

json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "nginx_container" "0.02%" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"

Es wird kein jq verwendet, aber es ist möglich args und environment in Werten zu verwenden.

CONTAINER=nginx_container json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "$CONTAINER" "$1" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"

1
NoamG

Hier ist eine Lösung, die die Optionen -R und -s zusammen mit transpose verwendet:

   split("\n")                       # [ "CONTAINER...", "nginx_container|0.02%...", ...]
 | (.[0]    | split("|")) as $keys   # [ "CONTAINER", "CPU%", "MEMUSAGE/LIMIT", ... ]
 | (.[1:][] | split("|"))            # [ "nginx_container", "0.02%", ... ] [ ... ] ...
 | select(length > 0)                # (remove empty [] caused by trailing newline)
 | [$keys, .]                        # [ ["CONTAINER", ...], ["nginx_container", ...] ] ...
 | [ transpose[] | {(.[0]):.[1]} ]   # [ {"CONTAINER": "nginx_container"}, ... ] ...
 | add                               # {"CONTAINER": "nginx_container", "CPU%": "0.02%" ...
1
jq170727
JSONSTR=""
declare -a JSONNAMES=()
declare -A JSONARRAY=()
LOOPNUM=0

cat ~/newfile | while IFS=: read CONTAINER CPU MEMUSE MEMPC NETIO BLKIO PIDS; do
    if [[ "$LOOPNUM" = 0 ]]; then
        JSONNAMES=("$CONTAINER" "$CPU" "$MEMUSE" "$MEMPC" "$NETIO" "$BLKIO" "$PIDS")
        LOOPNUM=$(( LOOPNUM+1 ))
    else
        echo "{ \"${JSONNAMES[0]}\": \"${CONTAINER}\", \"${JSONNAMES[1]}\": \"${CPU}\", \"${JSONNAMES[2]}\": \"${MEMUSE}\", \"${JSONNAMES[3]}\": \"${MEMPC}\", \"${JSONNAMES[4]}\": \"${NETIO}\", \"${JSONNAMES[5]}\": \"${BLKIO}\", \"${JSONNAMES[6]}\": \"${PIDS}\" }"
    fi 
done

Kehrt zurück:

{ "CONTAINER": "nginx_container", "CPU%": "0.02%", "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB", "MEM%": "0.16%", "NETI/O": "0B/0B", "BLOCKI/O": "22.09MB/4.096kB", "PIDS": "0" }
1
Nick Bull

Sie können Docker bitten, Ihnen JSON-Daten zu übergeben

docker stats --format "{{json .}}"

Weitere Informationen hierzu finden Sie unter: https://docs.docker.com/config/formatting/

Ich weiß, dass dies ein alter Beitrag ist, aber das Tool, das Sie suchen, heißt jo: https://github.com/jpmens/jo

Ein schnelles und einfaches Beispiel:

$ jo my_variable="simple"
{"my_variable":"simple"}

Ein bisschen komplexer

$ jo -p name=jo n=17 parser=false
{
  "name": "jo",
  "n": 17,
  "parser": false
}

Fügen Sie ein Array hinzu

$ jo -p name=jo n=17 parser=false my_array=$(jo -a {1..5})
{
  "name": "jo",
  "n": 17,
  "parser": false,
  "my_array": [
    1,
    2,
    3,
    4,
    5
  ]
}

Ich habe mit jo ein paar ziemlich komplexe Sachen gemacht, und das Schöne ist, dass Sie sich nicht darum kümmern müssen, Ihre eigene Lösung zu rollen, und sich nicht darum sorgen müssen, ob es möglich ist, ungültigen Json zu machen.

0
Jim

Wenn Sie mit Tabellendaten beginnen, ist es meiner Meinung nach sinnvoller, etwas zu verwenden, das mit Tabellendaten nativ funktioniert, wie sqawk , um es in json zu machen, und dann verwenden Sie jq weiter.

echo 'CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0' \
        | sqawk -FS '[|]' -RS '\n' -output json 'select * from a' header=1 \
        | jq '.[] | with_entries(select(.key|test("^a.*")|not))'

    {
      "CONTAINER": "nginx_container",
      "CPU%": "0.02%",
      "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
      "MEM%": "0.16%",
      "NETI/O": "0B/0B",
      "BLOCKI/O": "22.09MB/4.096kB",
      "PIDS": "0"
    }

Ohne jq ergibt sqawk etwas zu viel:

[
  {
    "anr": "1",
    "anf": "7",
    "a0": "nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0",
    "CONTAINER": "nginx_container",
    "CPU%": "0.02%",
    "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
    "MEM%": "0.16%",
    "NETI/O": "0B/0B",
    "BLOCKI/O": "22.09MB/4.096kB",
    "PIDS": "0",
    "a8": "",
    "a9": "",
    "a10": ""
  }
]