Skip to content

Instantly share code, notes, and snippets.

@blurayne
Last active September 11, 2019 16:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blurayne/3537d0c0536624bc03e4c7cea921c4f8 to your computer and use it in GitHub Desktop.
Save blurayne/3537d0c0536624bc03e4c7cea921c4f8 to your computer and use it in GitHub Desktop.
Variable types in BASH (typeof implementation)

Myth about associative arrays in BASH

To be clear: It's possible to pass associative arrays in BASH.

TL;DR you probably didn't RTFM so please njoy following session!

CODE

Pass an associative array the correct-BASH way

map_test() {
  local -n mymap="$1"
  echo "${!mymap[@]}"
  echo "${mymap[bar]}"
}

declare -A map=([foo]="tomcat" [bar]="kitty" [baz]="kitten")
map_test map

STDOUT

foo bar baz
kitty

modify a reference by reference or global decleation

Mind the -g flags (global). -x would stand for export to subshells.

map_test2() {
 local mymap="$1"
 map[baz]="bunny" # global
 declare -g ${mymap}[bar]="kill" # by reference
}

declare -Ag map=([foo]="tomcat" [bar]="kitty" [baz]="kitten")
map_test2 map
echo "${map[bar]} ${map[baz]}"

STDOUT

kill bunny

NOTES

  • eval and print "%q" is nice if you need to do un/quoting.
  • readarray or read -a creates an array where each element of the array is a line in the input
  • if strict bash coding doesn't fit you you can uncomment those lines!
#!/bin/bash
##
# Get type of a BASH variable (BASH ≥v4.0)
#
# To be used for bash functions with mixed arguments!
#
# LICENSE
#
# I dedicate any and all copyright interest in this software to the
# public domain. I make this dedication for the benefit of the public at
# large and to the detriment of my heirs and successors. I intend this
# dedication to be an overt act of relinquishment in perpetuity of all
# present and future rights to this software under copyright law.
#
# https://unlicense.org/
##
# Strict BASH mode
set -euo pipefail -o errtrace
##
# Get type of a BASH variable (BASH ≥v4.0)
#
# Notes
# - if references are encountered it will automatically try
# to resolve them unless `-f` is passed!
# - resolving functions can be seen as bonus since they also
# use `declare` (but with `-fF`). this behavior should be removed!
# - bad indicates bad referencing which normally shouldn't occur!
# - types are shorthand and associative arrays map to "map" for convenience
#
# argument
# -f (optional) force resolvement of first hit
# <variable-name> Variable name
#
# stdout
# (nil|int|arr|map|ref|fn|bad)
#
# stderr
# -
#
# return
# 0 - always
#
typeof() {
# __ref: avoid local to overwrite global var declaration and therefore emmit wrong results!
local type="" resolve_ref=true __ref="" signature=()
if [[ "$1" == "-f" ]]; then
# do not resolve reference
resolve_ref=false; shift;
fi
__ref="$1"
while [[ -z "${type}" ]] || ( ${resolve_ref} && [[ "${type}" == *n* ]] ); do
IFS=$'\x20\x0a\x3d\x22' && signature=($(declare -p "$__ref" 2>/dev/null || echo "na"))
if [[ "${signature}" == "na" ]]; then
if declare -F "$__ref" 1>/dev/null 2>&1; then
if [[ -n "${type}" ]]; then
# a reference to a function? BAD! does not work!
printf "bad"
return 0
fi
printf "fn"
return 0
fi
type=""
else
type="${signature[1]}" # could be -xn!
fi
if [[ -z "${__ref}" ]] || [[ "${type}" == "na" ]] || [[ "${type}" == "" ]]; then
printf "nil"
return 0
elif [[ "${type}" == *n* ]]; then
__ref="${signature[4]}"
fi
done
case "$type" in
*i*) printf "int";;
*a*) printf "arr";;
*A*) printf "map";;
*n*) printf "ref";;
*) printf "str";;
esac
}
echo "## do not resolve references"
echo
echo "# string"
str="Hello world!"
echo "declaration: $(declare -p str)"
echo "typeof : $(typeof -f str)"
echo
echo "# array"
arr=(foo bar baz)
echo "declaration: $(declare -p arr)"
echo "typeof : $(typeof -f arr)"
echo
echo "# associative array"
declare -A map=([foo]="tomcat" [bar]="kitty" [baz]="kitten")
echo "declaration: $(declare -p map)"
echo "typeof : $(typeof -f map)"
echo
echo "# function"
fn() { true; }
echo "declaration: $(declare -f fn | xargs printf "%s")"
echo "typeof : $(typeof -f fn)"
echo
echo "# undefined"
echo "declaration: n/a"
echo "typeof : $(typeof -f nonexistent)"
echo
echo "# reference"
declare -n ref="str"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof -f ref)"
echo
echo "# undefined reference"
declare -n ref="undefined"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof -f ref)"
echo
echo "## resolve references"
echo
echo "# resolve: undefined reference"
declare -n ref="undefined"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "# resolve: reference to string"
declare -n ref="str"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "# resolve: reference to array"
declare -n ref="arr"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "# resolve: reference to associative array"
declare -n ref="map"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "## recursion"
echo
echo "# resolve: reference to reference to string"
declare -n ref2="str"
declare -n ref="ref2"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "# resolve: reference to reference to undefined"
declare -n ref2="undefined"
declare -n ref="ref2"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "## special test cases"
echo
echo "# reference to function"
# TODO: function can be declared by `declare` but does it make sense checking for it?
declare -n ref="fn"
echo "declaration: $(declare -p ref)"
echo "typeof : $(typeof ref)"
echo
echo "# self-feference"
echo "covered by $(declare -n ref="ref" 2>&1 || true)"
echo
echo "# global declaration with export (multiple args in declare command)"
declare -agx arr2=(foo bar)
echo "declaration: $(declare -p arr2)"
echo "typeof : $(typeof arr2)"
echo
echo "# global declaration with export (multiple args in declare command)"
echo "declaration: $(declare -p arr2)"
echo "typeof : $(typeof arr2)"
echo
echo "# associative array passed to inside function"
inside_function_typeof() { local -n my_map="$1"; typeof my_map; }
inside_function_declare() { local -n my_map="$1"; declare -p my_map; }
inside_function_print_keys() { local -n my_map="$1"; echo "${!my_map[@]}"; }
echo "declaration: $(declare -f inside_function_declare)"
echo "inside typeof : $(inside_function_typeof map)"
echo "inside declaration: $(inside_function_declare map)"
echo "inside print : $(inside_function_print_keys map)"
echo
## do not resolve references
# string
declaration: declare -- str="Hello world!"
typeof : str
# array
declaration: declare -a arr=([0]="foo" [1]="bar" [2]="baz")
typeof : arr
# associative array
declaration: declare -A map=([foo]="tomcat" [bar]="kitty" [baz]="kitten" )
typeof : map
# function
declaration: fn(){true}
typeof : fn
# undefined
declaration: n/a
typeof : nil
# reference
declaration: declare -n ref="str"
typeof : ref
# undefined reference
declaration: declare -n ref="undefined"
typeof : ref
## resolve references
# resolve: undefined reference
declaration: declare -n ref="undefined"
typeof : nil
# resolve: reference to string
declaration: declare -n ref="str"
typeof : str
# resolve: reference to array
declaration: declare -n ref="arr"
typeof : arr
# resolve: reference to associative array
declaration: declare -n ref="map"
typeof : map
## recursion
# resolve: reference to reference to string
declaration: declare -n ref="ref2"
typeof : str
# resolve: reference to reference to undefined
declaration: declare -n ref="ref2"
typeof : nil
## special test cases
# reference to function
declaration: declare -n ref="fn"
typeof : bad
# self-feference
covered by incubator/assoc_array: line 237: declare: ref: nameref variable self references not allowed
# global declaration with export (multiple args in declare command)
declaration: declare -ax arr2=([0]="foo" [1]="bar")
typeof : arr
# global declaration with export (multiple args in declare command)
declaration: declare -ax arr2=([0]="foo" [1]="bar")
typeof : arr
# associative array passed to inside function
declaration: inside_function_declare ()
{
local -n my_map="$1";
declare -p my_map
}
inside typeof : map
inside declaration: declare -n my_map="map"
inside print : foo bar baz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment