How to port to bash-style arrays to ash?

Some time ago I have written a bash script which now should be able to run in environment with ash.

In bash it was like :

services=( "service1.service"
           "service2.service"                                       
           "service3.service" )  

for service in "${services[@]}"
do
   START $service                   
done

START()
{
   echo "Starting "$1
   systemctl start $1
}

In reality there are like 40 services in array, and I want to make this transition as painless and clean as possible. Have always been using bashisms. Now I’m in a pinch with the task to make scripts more portable.

For portability reasons probably it would be nice to have a pure ash solution. But since I have a pretty robust busybox at my disposal I might sacrifice some portability. Only if readability improves a lot, since “clean” script is a metric too.

What would be portable and clean solution in this case?

Asked By: metamorphling

||

ash does not have arrays. The only thing that comes close is the positional parameters, so you could do

set -- "service1.service" 
       "service2.service" 
       "service3.service"

for service in "$@"
do
   START $service
done
Answered By: glenn jackman

Before arrays were in bash, ksh, and other shells, the usual method was to pick a delimiter that wasn’t in any of the elements (or one that was uncommon to minimise any required escaping), and iterate over a string containing all the elements, separated by that delimiter. Whitespace is usually the most convenient delimiter choice because the shell already splits “words” by whitespace by default (you can set IFS if you want it to split on something different).

For example:

# backslash-escape any non-delimiter whitespace and all other characters that
# have special meaning to the shell, e.g. globs, parenthesis, ampersands, etc.
services='service1.service service2.service service3.service'

for s in $services ; do  # NOTE: do not double-quote $services here.
  START "$s"
done

$services should NOT be double-quoted here because we want the shell to split it into “words”.

Answered By: cas

If you need to refer to the list of services only once, you can use a here-doc:

while IFS= read -r service
do
   START "$service"
done << END
service1.service
service2.service
service3.service
END

Note that the service names should not be quoted in the list
(although "$service" probably should be quoted,
unless you have a good reason not to). 
If you would like to have the service names indented,
use <<- instead of << and indent the names with tabs:

while IFS= read -r service
do
   START "$service"
done <<- END
        service1.service
        service2.service
        service3.service
END

You can simulate an array by creating variables with fix prefix

Example 1:

#!/bin/ash

# simulate array variables
comment_wifi='wifi-text'
comment_status='some-state'
comment_0=1234
comment_1=7878
comment_2=9999

for v in wifi status $(seq 0 2)
do
 # construct variable and get its value
 eval value="$comment_${v}"

 echo "comment_${v}: ${value}"
done

Output:

comment_wifi: wifi-text
comment_status: some-state
comment_0: 1234
comment_1: 7878
comment_2: 9999

Example 2:

#!/bin/ash

# fill create and variables 
for v in wifi status $(seq 0 2)
do
  # example value
  a_value="$(cat /proc/uptime)"

  # construct variable and assign value
  eval comment_${v}="$a_value";
done

# output
for v in wifi status $(seq 0 2)
do
 # construct variable and get its value
 eval value="$comment_${v}";

 echo "comment_${v}: ${value}"
done

Output:

comment_wifi: 5954.23 22504.11
comment_status: 5954.24 22504.12
comment_0: 5954.24 22504.14
comment_1: 5954.25 22504.16
comment_2: 5954.25 22504.17
Answered By: ddmesh
Categories: Answers Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.