Last active
May 21, 2016 03:59
-
-
Save Jeff-Russ/12a21979022e1cd43a5e58aaeaa687fe to your computer and use it in GitHub Desktop.
faking nested arrays and hashes in bash3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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