Unfortunately, arrays are not available in POSIX shell specifications. Hence, you can't use array in any form in your shell script.
However, I will demonstrate some common tricks to workaround it with existing facility. It requires very high level of discipline since this is a hack solution. If you don't want hacking solution, you should stop and proceed to another chapter.
Remember that we can manipulate variables from name to value fields? We can make use of the general variable mechanism to create the array functionality.
First, we need to design the variable pattern in a list. My recommendation is to make use of double underscore (__) to create an unique naming pattern. This is to avoid getting conflict by the conventional variable creation. Then the no-index variable holds 2 properties:
Some good example would be:
__array_ARRAYNAME # hold the array length__array_ARRAYNAME_INDEX # each array elementSay for the an array of "country" as an example, the full list looks like:
__array_country=4__array_country_0="United States of America"__array_country_1="United Kingdom"__array_country_2="Canada"__array_country_3="Australia"Since array feature is not available in POSIX, we can only create them as a list of functionalities using the POSIX functions. This way, we can operate systematically to handle the large arrays.
Due to the limited features offered by the POSIX shell, we need at minimum of 1 dependency: eval. This is primarily used to construct the key field for array query. Example:
value="$("$(eval echo \$__array_${name}_${index})")"Keep in mind that by using eval, we have the tendency go into security issues. Hence, we should keep its usage as simple as possible.
Since this is a huge flock of variables, I would not advise you to provide an "export" feature. This can corrupt the environment variables and introduces a whole new level of security issues if you miss-handles any array element. Keep the use as only for internal needs.
Now that we level-set all the idea, we will review the Create, Read, Update, and Delete (CRUD) processes.
To create an array, we will only create all elements at once. To do that, we create the array_create function as follows:
#!/bin/sharray_create() { name="$1" shift i=0 while [ -n "$1" ]; do arr="__array_${name}_${i}" eval "$arr=\"$1\"" shift i=$((i + 1)) done arr="__array_${name}" eval "$arr=\"$i\""}# to create an array named countryarray_create "country" \ "United States of America" \ "United Kingdom" \ "Canada" \ "Australia"This will create the list of arrays demonstrated in the previous idea section. To check the existence of the array, we can check the length of the "array length" string is empty. Here is an example:
#!/bin/sharray_exists() { name="$1" length="$(echo "$(eval echo \$__array_${name})")" if [ -z "$length" ]; then return 1 fi return 0}# to check an array existsarray_exists "country"if [ "$?" != "0" ]; then 1>&2 echo "[ ERROR ] invalid array name." return 1fiHowever, you don't need to create existence checking on purpose. All we need to understand is that by checking the string length of the "array length", we can determine the array existence.
For read, we covers 3 specific read functions.
We can read the array by reconstructing the array field name and then extract its length value. For this function, we must practice silent is gold since we need to output the array length as value. Here is an example function:
#!/bin/sharray_length() { name="$1" length="$(echo "$(eval echo \$__array_${name})")" if [ -z "$length" ]; then 1>&2 echo "[ ERROR ] unknown array: $name" return 1 fi echo "$length"}# to use array_lengthlength="$(array_length "country")"To read a value of an element, we build the array_read_by_index function. Since we have the array_length function, we can simplify the need to check array existence. Here is an example:
#!/bin/sharray_read_by_index() { name="$1" index="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi arr="__array_${name}_${index}" value="$(eval echo \$__array_${name}_${index})" if [ ! -z "$value" ]; then echo "$value" fi}# to use array_read_by_indexvalue="$(array_read_by_index "country" "0")"
To read all the values in one go, we build the array_read_by_all function. This function loops through the arrays and print out the values. Here is an example:
#!/bin/dasharray_read_all() { name="$1" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi i=0 while [ $i -lt $length ]; do echo "$(eval echo \$__array_${name}_${i})" i=$((i + 1)) done unset i}# to use array_read_allvalue="$(array_read_all "country")"Now here comes the tricky part: updating an array. We look into updating the array element value and adding elements.
To update a value, we create the array_update function. This function leverages the array_length function for existence validation.
#!/bin/sharray_update() { name="$1" index="$2" value="$3" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi arr="__array_${name}_${index}" eval "$arr=\"$value\""}# to use array_updatearray_update "country" "3" "Africa"To add a new element into the array, we build the array_add function. This function leverages the array_length function for existence validation.
#!/bin/sharray_add() { name="$1" value="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi arr="__array_${name}_${length}" eval "$arr=\"$value\"" arr="__array_${name}" length=$((length + 1)) eval "$arr=\"$length\""}# to use array_addarray_add "country" "Taiwan"To add an element at a given index, we create array_add_by_index function. This function leverages the array_length function for existence validation.
#!/bin/sharray_add_by_index() { name="$1" index="$2" value="$3" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -gt $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi i=0 while [ $i -le $length ]; do if [ $i -ge $index ]; then temp="$(echo "$(eval echo \$__array_${name}_${i})")" arr="__array_${name}_${i}" eval "$arr=\"$value\"" value="$temp" fi i=$((i + 1)) done unset i arr="__array_${name}" length=$((length + 1)) eval "$arr=\"$length\""}# to use array_add_by_indexarray_add_by_index "country" "2" "Mongolia"Lastly, we look into delete functions for all arrays.
To delete an element, we build the array_delete_element function. This function leverages the array_length function for existence validation. Here is an example:
#!/bin/dasharray_delete_element() { name="$1" index="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi i=0 while [ $i -lt $length ]; do if [ $i -ge $index ]; then arr="__array_${name}_${i}" value="$(echo "$(eval echo \$__array_${name}_$((i + 1)))")" eval "$arr=\"$value\"" fi if [ $i -eq $length ]; then arr="__array_${name}_${i}" unset ${arr} fi i=$((i + 1)) done unset i arr="__array_${name}" length="$(echo "$(eval echo \$__array_${name})")" length=$((length - 1)) eval "$arr=\"$length\""}# to use array_delete_elementarray_delete_element "country" "3"To delete the entire array, we build the array_delete function. This function leverages the array_length function for existence validation. Here is an example:
#!/bin/dasharray_delete() { name="$1" length="$(echo "$(eval echo \$__array_${name})")" if [ -z "$length" ]; then 1>&2 echo "[ ERROR ] unknown array: $name" return 1 fi i=0 while [ $i -lt $length ]; do arr="__array_${name}_${i}" unset "${arr}" i=$((i + 1)) done arr="__array_${name}" unset "$arr"}# to use array_deletearray_delete "country"POSIX shell does not support associative array. If you need it, you might want to consider using BASH instead. Mashing 2 sets of array functions can create a very lengthy shell script.
That's all about working around for array feature. However, I wouldn't recommend you to deploy such solution. You might look into trouble due to its hacked solution nature.
Just for future reference, this is the full array library for POSIX shell, which is based on the learning from the above. You can source the library into your shell script to get it work OR expand from here.
#!/bin/sh_array_get_name() { name="$1" index="$2" if [ -z "$name" ]; then return 1 fi if [ -z "$index" ]; then echo "__array_${name}" return 0 fi echo "__array_${name}_${index}"}array_create() { name="$1" shift i=0 while [ -n "$1" ]; do arr="$(_array_get_name "$name" "$i")" eval "$arr=\"$1\"" shift i=$((i + 1)) done arr="$(_array_get_name "$name")" eval "$arr=\"$i\""}array_length() { name="$1" length="$(echo "$(eval echo \$__array_${name})")" if [ -z "$length" ]; then 1>&2 echo "[ ERROR ] invalid array: $name" return 1 fi echo "$length" return 0}array_read_by_index() { name="$1" index="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi arr="$(_array_get_name "$name" "$index")" value="$(eval echo \$__array_${name}_${index})" if [ ! -z "$value" ]; then echo "$value" fi}array_read_all() { name="$1" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi i=0 while [ $i -lt $length ]; do echo "$(eval echo \$__array_${name}_${i})" i=$((i + 1)) done unset i}array_update() { name="$1" index="$2" value="$3" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi arr="$(_array_get_name "$name" "$index")" eval "$arr=\"$value\""}array_add() { name="$1" value="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi arr="$(_array_get_name "$name" "$length")" eval "$arr=\"$value\"" arr="$(_array_get_name "$name")" length=$((length + 1)) eval "$arr=\"$length\""}array_add_by_index() { name="$1" index="$2" value="$3" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -gt $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi i=0 while [ $i -le $length ]; do if [ $i -ge $index ]; then temp="$(echo "$(eval echo \$__array_${name}_${i})")" arr="__array_${name}_${i}" eval "$arr=\"$value\"" value="$temp" fi i=$((i + 1)) done unset i arr="__array_${name}" length=$((length + 1)) eval "$arr=\"$length\""}array_delete_element() { name="$1" index="$2" length="$(array_length "$name")" if [ $? -ne 0 ]; then return 1 fi if [ $index -lt 0 ] || [ $index -ge $length ]; then 1>&2 echo "[ ERROR ] incorrect index: $index" return 1 fi i=0 while [ $i -lt $length ]; do if [ $i -ge $index ]; then arr="$(_array_get_name "$name" "$i")" value="$(echo \ "$(eval echo \$__array_${name}_$((i + 1)))")" eval "$arr=\"$value\"" fi if [ $i -eq $length ]; then arr="__array_${name}_${i}" unset ${arr} fi i=$((i + 1)) done unset i arr="$(_array_get_name "$name")" length="$(echo "$(eval echo \$__array_${name})")" length=$((length - 1)) eval "$arr=\"$length\""}array_delete() { name="$1" length="$(echo "$(eval echo \$__array_${name})")" if [ -z "$length" ]; then 1>&2 echo "[ ERROR ] unknown array: $name" return 1 fi i=0 while [ $i -lt $length ]; do arr="$(_array_get_name "$name" "$i")" unset "${arr}" i=$((i + 1)) done unset i arr="$(_array_get_name "$name")" unset "$arr"}