This is part 25 of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.

See part 1 if you want to catch the series from the start.

Last time, we discussed passing arrays as arguments to functions. This time, let’s discuss passing hashes instead.

While we were able to find a relatively convenient method of serializing and deserializing arrays by using the ascii unit separator along with the splat expansion and the local command, the same can’t be done for hashes.

The local trick is problematic because it’s difficult to turn a hash into a hash literal without writing code to do it. And, unfortunately, that’s about the size of it. We’re going to have to write code if we want to find a way to serialize a hash easily.

So what are we waiting for? Let’s write a test using our template:

IFS=$'\037'
set -o noglob
set -o nounset
shopt -s expand_aliases
alias it='(_shpec_failures=0; alias setup &>/dev/null && { setup; unalias setup; alias teardown &>/dev/null && trap teardown EXIT ;}; it'
alias ti='return $_shpec_failures); (( _shpec_failures += $?, _shpec_examples++ ))'
alias end_describe='end; unalias setup teardown &>/dev/null'

source $(dirname $(readlink -f $BASH_SOURCE))/../lib/support.bash

describe rep
  it "generates a representation of a hash"
    declare -A samples=(
      [zero]=0
      [one]=1
    )
    result=$(rep samples)
    assert equal '([one]="1" [zero]="0" )' $result
  ti
end_describe

The idea here is to take the declare -p output and trim off the first part of the declaration, the part in which the variable name appears. I’ll go straight to the working version:

rep () {
  local expression

  expression="^declare -[a|A] [^=]+='(.*)'$"
  [[ $(declare -p $1) =~ $expression ]] && echo ${BASH_REMATCH[1]}
}

Here is how it’s used:

myfunc () {
  local -A myhash=$1

  # inspect the result to see that it worked
  declare -p myhash
}

declare -A argument=([zero]=0)

myfunc $(rep argument)

While this isn’t as clean as the array passing in the last post, this is the cleanest it can get while still passing by value. As compared to the array passing, there’s an additional call to rep in order to feed myfunc. Unfortunately that seems unavoidable.

At least the implementation of rep is simple, thanks to declare -p. In fact, since we’re using declare, rep isn’t specific to hashes either…if you like this method better than the array-passing method, it works for arrays as well if you just change the local -A to local -a. In fact, it also gets around the sparse array issue since declare -p provides the correct indexes.

Continue with part 26 - returning values