Frontier Software

Indexed Arrays

Something I’ve found a lot harder than it should be is converting JSON arrays to bash “indexed” (as in not associative) arrays.

JSON arrays are comma delimited, similar to csv but with surrounding square brackets. There are many ways to do this. The old way involves temporarily resetting the internal field separator environment variable IFS from its default space, TAB, NEWLINE to a comma, which shellcheck and others frown upon.

The modern way involves using readarray (aka mapfile, the two are synonymous).

The man pages linked to above show mapfile’s is slightly less terse than readarray’s for some reason, but I prefer using readarray since it’s clearer to me what the command does.

Instead of IFS=’,’, readarray uses -d ',' and -t ensures no trailing NEWLINE problem. My efforts with using read -ar as recommended by shellcheck broke the code because the string inside the square brackets was treated as a unit instead of split on commas.

#!/bin/bash

# readarray -t arr <<< "$(jsonarray2lines '["Dakes","Emile Alexander"]')"
function jsonarray2lines {
  local val
  local key
  local arr
  val="$1"
  val="${val#\[}"
  val="${val%\]}"
  val="${val//\"/}"
  readarray -d ',' -t arr <<< "${val}"
  for key in "${arr[@]}"; do
    echo "$key"
  done
}

ExampleGroup 'iterate through JSON array'

  Example 'single word'
    When call jsonarray2lines '["Appel"]'
    The output should equal 'Appel'
    The status should be success
    The error should be blank
  End

  Example 'find a phrase in the text'
    When call jsonarray2lines '["Jakkie Louw"]'
    The output should equal 'Jakkie Louw'
    The status should be success
    The error should be blank
  End

  Example 'find two phrases in the text'
    When call jsonarray2lines '["Dakes","Emile Alexander"]'
    The output should equal 'Dakes
Emile Alexander'
    The status should be success
    The error should be blank
  End

  Example 'no matches'
    When call jsonarray2lines ''
    The output should equal ''
    The status should be success
    The error should be blank
  End

End

The above function involves the fairly common task of converting text delimited by something — a comma in this case — to an array, and to me the simplest way would be

IFS=',' arr=($var)

The above passes shellspec, but causes shellcheck to barf

IFS=',' arr=(${val})
             ^----^ SC2206 (warning): Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a.

Quoting ${val} causes “Dakes,Emile Alexander” which then isn’t split by IFS. Rewriting it as

IFS=',' read -a arr <<< "${val}"

again passes shellspec, and changes shellcheck’s barf from brown to green, which I’m guessing is better.

  IFS=',' read -a arr <<< "${val}"
          ^--^ SC2162 (info): read without -r will mangle backslashes.

Adding the -r (to strip NEWLINE which isn’t in the compacted JSON array) causes empty arrays to be created.

Finally

mapfile -d ',' -t arr <<< "${val}"

Made both shellspec and shellcheck happy.

I’ve previousy encountered readarray and was really confused about how it differs from mapfile. Turns out they are synonyms.