Skip to content

Instantly share code, notes, and snippets.

@Jeff-Russ
Last active May 21, 2016 03:59
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 Jeff-Russ/12a21979022e1cd43a5e58aaeaa687fe to your computer and use it in GitHub Desktop.
Save Jeff-Russ/12a21979022e1cd43a5e58aaeaa687fe to your computer and use it in GitHub Desktop.
faking nested arrays and hashes in bash3
#!/bin/sh
# faux nesting for bash 3 using indexes or keys.
# See explanation below
function nested {
# find array referred to by arg 1
local container=${1}[@]
local container=(${!container})
# if arg 2 an integer, use normally:
if [[ $2 =~ ^-?[0-9]+$ ]]; then
local arg2=$2;
else # if it's a string, find it's index:
for i in "${!container[@]}"; do
if [[ "${container[$i]}" = "$2" ]]; then
arg2=$i; break
fi
done
fi
# get inner value:
local inner_arr=${container[$arg2]}[@]
local contents=(${!inner_arr})
if [ $# -eq 4 ] # if 4th arg, use as "return"
then eval "$4=${contents[$3]}"
else echo "${contents[$3]}" # else: echo it
fi
}
# : 'create any number of arrays and then create another array that is a list
# of all the array names you want to "nest"'
# outer=(
# inner_array
# other_array
# )
# inner_array=(
# "inner_array:0"
# "inner_array:1"
# )
# other_array=(
# "other_array:0"
# "other_array:1"
# )
# : 'Each nested array can be accessed via its index in the "container"
# by index:'
# nested outer 1 1 #=> echos "other_array:1"
# : 'by name:'
# nested outer other_array 1 #=> echos "other_array:1"
# : 'Save to var:'
# var=$(nested outer 1 1)
# : "Or pass a 4th arg which will be mutated. It need not be initialized,
# it's first appearance can be right in the call:"
# nested outer 1 1 var # this also supresses echo
# echo $var #=> echos "other_array:1"
#!/bin/sh
# faking nested arrays and hashes in bash3
printf "\n############# FAKING 3D ARRAYS #################################\n"
array_0=(
"I pretend to be array[0][0]"
"I pretend to be array[0][1]"
"I pretend to be array[0][2]"
)
array_1=(
"I pretend to be array[1][0]"
"I pretend to be array[1][1]"
"I pretend to be array[1][2]"
"I pretend to be array[1][3]"
"I pretend to be array[1][4]"
)
printf "\nget [0][0] ...\n"
echo ${array_0[0]}
printf "\nget all elements in [0] ...\n"
for str in "${array_0[@]}"; do
echo "$str"
done
printf "\nget all elements in [1] starting at [0][1] ...\n"
for str in "${array_1[@]:1}"; do
echo "$str"
done
printf "\n\n============= FAKING NESTED HASHES (sort of) =============n"
printf "\nThe goal here is to come as close as possible having: faux_hash[inner_hash][2]\n\n"
faux_hash=(
"some_list"
"another_list"
)
# # uncomment to test different behaivor with arrays
# faux_hash=($faux_hash)
some_list=(
"some_list:0"
"some_list:1"
"some_list:2"
)
another_list=(
"another_list:0"
"another_list:1"
"another_list:2"
"another_list:3"
"another_list:4"
)
# # uncomment to test different behaivor with arrays
# some_list=($some_list)
# another_list=($another_list)
echo "============= DIRECT ACCESS OF INNER DATA ==============================="
printf "\n---- random access with \${some_list[2]} ----\n"
echo ${some_list[2]} # this won't work if some_list=($some_list)
echo
# same here!
printf "\n---- iterative access with for val in \"\${some_list[@]}\" ----\n"
for val in "${some_list[@]}"; do
echo $val
done
echo "============= MASS ACCESS OF INNER DATA ================================="
printf "\n---- get all keys in faux_hash ----\n"
for key in "${faux_hash[@]}"; do
echo "$key"
done
printf "\n---- print each list 'in' faux_hash 'container' ----\n"
for key in "${faux_hash[@]}"; do
echo "We are working on \$key, which is '$key'"
key_append=$key[@]
echo "\$key_append appends '[@]' to value of \$key, producing: '$key_append'"
contents=${!key_append}
echo "We can extract the values from the inner list with \${!key_append}"
echo "Contents extracted from $key_append: ( $contents )"
echo ${contents[2]} # this won't work. see below
done
# parameter expansion with ! takes $key and treats it's value as a new variable
# name. This would be bad paramter expansion even though it looks like what we want:
# ${$key}
# bash cannot do this. it needs ! instead of the inner $
# The reason ${contents[2]} doesn't work is because $contents is not an array,
# so we don't have access to subscripts. If we do `contents=($contents)` first
# to array-ify it, it will work. If you try to array-ify the lists from the start
# you will notice that the iterator only has access to the first element and that
# each $content only shows the first element as well. In addition, `${contents[2]}`
# still won't work.
echo "============= RANDOM INDERECT ACCESS OF INNER DATA ======================="
# the goal here is to come as close as possible to doing faux_hash[some_list][2]
# and being able to iterate faux_hash[some_list] as if it is an actual collection.
echo "\${faux_hash[i]} returns names of unrelated lists as string: ${faux_hash[1]}"
echo "but we want to be able to access 'inner' lists via indexes, not just their names"
# this actually works!
i1=0; i2=1
first_list=${faux_hash[$i1]}[@]
contents=(${!first_list})
echo ${contents[$i2]}
# We could just put this in a function. Too bad bash can only return int error
# codes. The workaround is to have the function modify an argument
printf "\n---- via dedicated getter function ----\n"
# This is a self contained solution that replaces faux_hash as a container and is
# instead is a sort of smart container, almost like a class method.
# You could also choose to keep the data separate but the function still will
# only work with that data only since it refers to it by name
function hashes {
container=(
"some_list"
"another_list"
)
i1=$1; i2=$2
local first_list=${container[$i1]}[@]
local contents=(${!first_list})
eval "$3=${contents[$i2]}"
}
hashes 1 1 result
echo $result
# this is better than a 3D array because we do have keys which are names if we
# want to access data that way, but we aren't bound to them so we can also
# iterate without knowing them. But it would be nice if it was reuseable.
printf "\n---- via reuseable getter function ----\n"
function get_inner {
local container=${1}[@]
local container=(${!container})
local i1=$2; local i2=$3
local first_list=${container[$i1]}[@]
local contents=(${!first_list})
eval "$4=${contents[$i2]}"
}
get_inner faux_hash 1 0 result
echo $result
printf "\n\n############# FAKING NESTED ARRAYS IN FAKE HASH #################################\n"
printf "\nThe goal here is to come as close as possible having: hashes[inner_array][2]\n\n"
# This acts as a container for the other arrays which we cannot actually place inside.
# Instead we simply place the name of the arrays we would like to pretend it contains.
hashes=(
"inner_array"
"other_array"
)
# Here are the two 'inner' arrays that the fake outer container refers to:
inner_array=(
"inner_array:0"
"inner_array:1"
"inner_array:2"
)
other_array=(
"other_array:0"
"other_array:1"
"other_array:2"
"other_array:3"
"other_array:4"
)
# We had this function to accept `get_inner hashes 1 2 result` but we want a
# name instead of index number for the second argument.
# function get_inner {
# local container=${1}[@]
# local container=(${!container})
# local inner_arr=${container[$2]}[@]
# local contents=(${!inner_arr})
# if [ $# -eq 4 ]
# then eval "$4=${contents[$3]}"
# else echo "${contents[$3]}"
# fi
# }
printf "\n\n================== GET INDEX FROM VALUE =======================n\n"
# We can make somthing that gets the index number from the value.
# This works but it's not reuseable:
my_array=(red orange green)
value='green'
echo "Naked loop: If this returns '2' it worked:"
for i in "${!my_array[@]}"; do
if [[ "${my_array[$i]}" = "${value}" ]]; then
echo "${i}"
break
fi
done
printf "\n\n============== GET INDEX FROM VALUE FUNCTION ==================n\n"
# we want to put this in a function and make `my_array` and `value` arguments.
function get_index {
local container=${1}[@]
local container=(${!container})
local value=$2
for i in "${!container[@]}"; do
if [[ "${container[$i]}" = "${value}" ]]; then
if [ $# -eq 3 ]
then eval "$3=$i"
else echo "${i}"
fi
break
fi
done
}
get_index my_array green return
echo "Mutator function: If this returns '2' it worked:"
echo $return
echo
echo "Echoing function: If this returns '2' it worked:"
get_index my_array green
# Thing might be a nice function to use by itself but we want to inject it into
# the get_inner function. We could make a new function called get_by_name or
# something but it would be nice to make get_inner just adapt when it sees a string.
printf "\n\n============== MAKING get_inner ACCEPTS KEYS ==================n\n\n"
# get_inner, improved:
function get_inner {
# find array referred to by arg 1
local container=${1}[@]
local container=(${!container})
# if arg 2 an integer, use normally:
if [[ $2 =~ ^-?[0-9]+$ ]]; then
local arg2=$2;
else # if it's a string, find it's index:
for i in "${!container[@]}"; do
if [[ "${container[$i]}" = "$2" ]]; then
arg2=$i; break
fi
done
fi
local inner_arr=${container[$arg2]}[@]
local contents=(${!inner_arr})
if [ $# -eq 4 ] # if 4th arg, use as "return"
then eval "$4=${contents[$3]}"
else echo "${contents[$3]}" # else: echo it
fi
}
# hashes has two keys "inner_array" and "other_array", indexes 0 and 1.
# get_inner hashes 1 1 should now be the same as:
# get_inner hashes other_array 1
# so let's test that:
echo "Normal mode (int index): If this returns 'other_array:1' it worked:"
get_inner hashes 1 1
echo
echo "Hash mode (string index): If this returns 'other_array:1' it worked:"
get_inner hashes other_array 1
echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment