Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Enhance @sh to output objects as bash associative array initialisers #2249

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rah2501
Copy link

@rah2501 rah2501 commented Jan 13, 2021

Bash allows associative arrays to be initialised using declarations of
the form

name=([foo]=1 [bar]=42 [baz]="hello")

At present, the @sh filter can format arrays into space-separated
strings:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | [.[]] | @sh' | head -n 3
'lo' 'inet' '127.0.0.1'
'lo' 'inet6' '::1'
'eth0' 'inet' '93.93.131.233'

We alter the @sh filter to output objects as well, in a
format suitable for use as bash associative array initialisers:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | @sh' | head -n 3
[ifname]='lo' [family]='inet' [address]='127.0.0.1'
[ifname]='lo' [family]='inet6' [address]='::1'
[ifname]='eth0' [family]='inet' [address]='93.93.131.233'

Allowing us to use symbolic names to reference values:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | @sh' | while read initialiser; do declare -A iface; eval iface=($initialiser); echo "Interface ${iface[ifname]} has ${iface[family]} address ${iface[address]}"; done | head -n 3
Interface lo has inet address 127.0.0.1
Interface lo has inet6 address ::1
Interface eth0 has inet address 93.93.131.233

@coveralls
Copy link

coveralls commented Jan 13, 2021

Coverage Status

Coverage decreased (-16.0%) to 68.149% when pulling 264bd1f on rah2501:sh-associative-array-initialiser into 80052e5 on stedolan:master.

@rah2501 rah2501 changed the title Enhance @sh to output objects as bash associative array initialisers WIP: Enhance @sh to output objects as bash associative array initialisers Jan 14, 2021
Bash allows associative arrays to be initialised using declarations of
the form

  name=([foo]=1 [bar]=42 [baz]="hello")

At present, the @sh filter can format arrays into space-separated
strings:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | [.[]] | @sh' | head -n 3
'lo' 'inet' '127.0.0.1'
'lo' 'inet6' '::1'
'eth0' 'inet' '93.93.131.233'

We alter the @sh filter to output objects as well, in a
format suitable for use as bash associative array initialisers:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | @sh' | head -n 3
[ifname]='lo' [family]='inet' [address]='127.0.0.1'
[ifname]='lo' [family]='inet6' [address]='::1'
[ifname]='eth0' [family]='inet' [address]='93.93.131.233'

Allowing us to use symbolic names to reference values:

$ ip -j addr | jq -c -r '.[] | {ifname, addr_info: .addr_info[]} | {ifname, family: .addr_info["family"], address: .addr_info["local"]} | @sh' | while read initialiser; do declare -A iface; eval iface=($initialiser); echo "Interface ${iface[ifname]} has ${iface[family]} address ${iface[address]}"; done | head -n 3
Interface lo has inet address 127.0.0.1
Interface lo has inet6 address ::1
Interface eth0 has inet address 93.93.131.233
@rah2501 rah2501 force-pushed the sh-associative-array-initialiser branch from cb904b4 to 264bd1f Compare January 14, 2021 00:44
@rah2501 rah2501 changed the title WIP: Enhance @sh to output objects as bash associative array initialisers Enhance @sh to output objects as bash associative array initialisers Jan 14, 2021
@geirha
Copy link

geirha commented Jun 1, 2021

I see some problems with this.

Firstly, the current @sh produces a string that can be safely injected into a context where a word is expected in any sh shell. This change causes it to also produce strings that can only be injected into certain contexts of a bash script. In that regard, creating a new @bash format would be better

Second, keys of a json object can contain characters that are special to bash, but you only quoted the value, not the key. The key must be quoted as well.

It's also already possible to achieve the goal of populating an associative array from a single level object, with for example to_entries | map(@sh"data[\(.key)]=\(.value)"):

ip -j addr show dev lo |
jq -r '
  .[] | 
  {ifname, addr_info: .addr_info[]} |
  {ifname, family: .addr_info["family"], address: .addr_info["local"]} |
  to_entries |
  map(@sh"data[\(.key)]=\(.value)") |
  join(" ") + "\u0000"
' |
while read -rd '' entry; do
  declare -A data=()
  eval "$entry"
  printf 'Interface %s has %s address %s\n' "${data[ifname]}" "${data[family]}" "${data[address]}"
done
Interface lo has inet address 127.0.0.1
Interface lo has inet6 address ::1

Though in this case I'd more likely just set three variables instead; @sh"ifname=\(.ifname) family=\(.family) address=\(.address)\u0000"

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants