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 element
Say 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/sh
array_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 country
array_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/sh
array_exists() {
name="$1"
length="$(echo "$(eval echo \$__array_${name})")"
if [ -z "$length" ]; then
return 1
fi
return 0
}
# to check an array exists
array_exists "country"
if [ "$?" != "0" ]; then
1>&2 echo "[ ERROR ] invalid array name."
return 1
fi
However, 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/sh
array_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_length
length="$(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/sh
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_${name}_${index}"
value="$(eval echo \$__array_${name}_${index})"
if [ ! -z "$value" ]; then
echo "$value"
fi
}
# to use array_read_by_index
value="$(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/dash
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
}
# to use array_read_all
value="$(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/sh
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_${name}_${index}"
eval "$arr=\"$value\""
}
# to use array_update
array_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/sh
array_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_add
array_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/sh
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\""
}
# to use array_add_by_index
array_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/dash
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_${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_element
array_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/dash
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_${name}_${i}"
unset "${arr}"
i=$((i + 1))
done
arr="__array_${name}"
unset "$arr"
}
# to use array_delete
array_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"
}