简体   繁体   中英

Transform json to ini files using jq + bash

I'm trying to transform json (array of objects) to ini files:

[
  {
    "connection": {
      "id": "br0",
      "uuid": "ab1dd903-4786-4c7e-a4b4-3339b144d6c7",
      "stable-id": "",
      "type": "bridge",
      "interface-name": "br0",
      "autoconnect": "no",
      "autoconnect-priority": "0",
      "autoconnect-retries": "-1",
      "auth-retries": "-1",
      "timestamp": "44444",
      "read-only": "no",
      "permissions": "",
      "zone": "WAN",
      "master": "",
      "slave-type": "",
      "autoconnect-slaves": "1",
      "secondaries": "",
      "gateway-ping-timeout": "0",
      "metered": "unknown",
      "lldp": "default"
    },
    "ipv4": {
      "method": "manual",
      "dns": "192.168.1.1,192.168.2.1",
      "dns-search": "",
      "dns-options": " ",
      "dns-priority": "0",
      "addresses": "192.168.1.3/24",
      "gateway": "",
      "routes": "192.168.10.0/24 192.168.1.1",
      "route-metric": "-1",
      "route-table": "0",
      "ignore-auto-routes": "no",
      "ignore-auto-dns": "no",
      "dhcp-client-id": "",
      "dhcp-timeout": "0",
      "dhcp-send-hostname": "yes",
      "dhcp-hostname": "",
      "dhcp-fqdn": "",
      "never-default": "no",
      "may-fail": "yes",
      "dad-timeout": "-1"
    }
  },
  {
    "connection": {
      ...
    },
  }
]

OR

  {
    "connection": {
      ...
    },
  }

What i tried to do is:

1. Transform json to strings

data=$(jq -r 'def keyValue:  to_entries[] | "[\(.key)]\\n\(.value | to_entries|map("\(.key)=\(.value)" ) | join("\\n") )\\n"; if type == "array" then keys[] as $k | "\(.[$k] | .connection.id)=\(.[$k] | keyValue)" elif type == "object" then "\(.connection.id)=\(. | keyValue)" else keys end' /tmp/json)

Provides:

br1=[connection]\nid=br1\nuuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7\nstable-id=\ntype=fff\ninterface-name=br0\nautoconnect=no\nautoconnect-priority=0\nautoconnect-retries=-1\nauth-retries=-1\ntimestamp=1525494904\nread-only=no\npermissions=\nzone=WAN\nmaster=\nslave-type=\nautoconnect-slaves=1\nsecondaries=\ngateway-ping-timeout=0\nmetered=unknown\nlldp=default\n
br1=[802-3-ethernet]\nport=\nspeed=0\nduplex=\nauto-negotiate=no\nmac-address=\ncloned-mac-address=\ngenerate-mac-address-mask=\nmac-address-blacklist=\nmtu=1500\ns390-subchannels=\ns390-nettype=\ns390-options=\nwake-on-lan=default\nwake-on-lan-password=\n....

2. Walk over strings in bash

while IFS="=" read -r key value; do [ "$oldkey" = "$key" ] && echo -en "$value" >> "/tmp/ini/$key" || echo -en "$value" > "/tmp/ini/$key" ; oldkey="$key"; done <<< "$data"

Gives:

[connection]
id=br1
uuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7
stable-id=
type=fff
interface-name=br0
autoconnect=no
autoconnect-priority=0
autoconnect-retries=-1
auth-retries=-1
timestamp=1525494904
read-only=no
permissions=
zone=WAN
master=
slave-type=
autoconnect-slaves=1
secondaries=
gateway-ping-timeout=0
metered=unknown
lldp=default
[ipv4]
method=manual
dns=192.168.1.1,192.168.2.1
dns-search=
dns-options= 
dns-priority=0
addresses=192.168.1.3/24
gateway=
routes=192.168.10.0/24 192.168.1.1
route-metric=-1
route-table=0
ignore-auto-routes=no
ignore-auto-dns=no
dhcp-client-id=
dhcp-timeout=0
dhcp-send-hostname=yes
dhcp-hostname=
dhcp-fqdn=
never-default=no
may-fail=yes
dad-timeout=-1

I'm almost there! But is there possible to do it more "elegantly" and more performance way, avoiding pipes, external calls, etc.

Note: Moreover, mostly it should be done with jq + bash, because other processing tools like sed, awk is slower than i've done, but i do not reject them completely =)

PS - Main purpose of this transforming is the fast "bulk operation" to write ini files

To convert an array as shown to the ".ini" format could be accomplished by simply using this jq program:

def kv: to_entries[] | "\(.key)=\(.value)";

.[]
| to_entries[]
| "[\(.key)]", (.value|kv)

Putting this in a file, say program.jq, then assuming the JSON shown in the question (minus the "..." part) is in input.json, the following invocation:

jq -rf program.jq input.json

yields the corresponding ".ini" file.

If you want to ensure that the program will also handle the case when there is no enclosing array, you could modify the first line in the main program above to test whether the input is an array, so you'd have:

if type == "array" then .[] else . end
| to_entries[]
| "[\(.key)]", (.value|kv)

If the ultimate goal is to produce several .ini files, then we can reuse def kv as defined in the other answer:

def kv: to_entries[] | "\(.key)=\(.value)";

The driver program would however now be:

.[]
| [to_entries[] | "[\(.key)]", (.value|kv)]
| join("\n")

Running jq with this program then yields one JSON string for each item in the array. Using the example, the first such string would be:

"[connection]\nid=br0\nuuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7\nstable-id=\ntype=bridge\ninterface-name=br0\nautoconnect=no\nautoconnect-priority=0\nautoconnect-retries=-1\nauth-retries=-1\ntimestamp=44444\nread-only=no\npermissions=\nzone=WAN\nmaster=\nslave-type=\nautoconnect-slaves=1\nsecondaries=\ngateway-ping-timeout=0\nmetered=unknown\nlldp=default\n[ipv4]\nmethod=manual\ndns=192.168.1.1,192.168.2.1\ndns-search=\ndns-options= \ndns-priority=0\naddresses=192.168.1.3/24\ngateway=\nroutes=192.168.10.0/24 192.168.1.1\nroute-metric=-1\nroute-table=0\nignore-auto-routes=no\nignore-auto-dns=no\ndhcp-client-id=\ndhcp-timeout=0\ndhcp-send-hostname=yes\ndhcp-hostname=\ndhcp-fqdn=\nnever-default=no\nmay-fail=yes\ndad-timeout=-1"

You can then iterate over these newline-delimited strings.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM