2

I have a small script called test.sh which prints the value based on the index given.

#!/bin/sh
days_in_month=(0 31 28 31 30 31 30 31 31 30 31 30 31)
echo "${days_in_month[$1]}"

The above code works fine if I execute using bash.

$ bash test.sh 1
31

But I need to execute the same script in an embedded board which has sh as part of Busybox package. When I run the command in that board, it throws an error.

$ sh test.sh
test.sh: line 2: syntax error: unexpected "("

I observe that the same error is thrown when I use dash instead of bash in Ubuntu system.

$ dash test.sh
test.sh: line 2: syntax error: unexpected "("

Is there any way that I could change the code so that Busybox sh will execute without any errors?

NayabSD
  • 1,112
  • 2
  • 15
  • 26

3 Answers3

4

Both busybox' sh (which isash) and dash do not support arrays.

You could either write a big if-else or switch case statement, or use the following trick. We simulate an array using a single string with spaces as delimiters.

cut -d ' ' -f "$1" <<< "31 28 31 30 31 30 31 31 30 31 30 31"

or even more portable:

echo "31 28 31 30 31 30 31 31 30 31 30 31" | cut -d ' ' -f "$1"

Another workaround is to abuse the script's positional parameters as seen in this answer.

Socowi
  • 25,550
  • 3
  • 32
  • 54
  • Got the job done. Thanks. But still, I am worried how to achieve that using arrays in dash. – NayabSD Jun 13 '18 at 09:09
  • 1
    @NayabBashaSayed [dash does not support arrays](https://stackoverflow.com/a/15004747/6770384). – Socowi Jun 13 '18 at 09:16
  • Oh. Thank you.. Would you please elaborate the answer with this information. I will mark it as accepted answer. – NayabSD Jun 13 '18 at 09:31
  • 2
    Note, though, that here-strings are _also_ an extension to the standard `sh` shell, and I'm slightly surprised that `ash` supports them. – Kusalananda Jun 13 '18 at 12:08
  • You can replace a here string with a single-line here document, which is POSIX-compatible. – chepner Jun 13 '18 at 20:10
2

/bin/sh in general does not support arrays, apart from the list of positional parameters.

Let's use that:

#/bin/sh

pos=$1

if [ "$pos" -lt 1 ] || [ "$pos" -gt 12 ]; then
    printf 'No such month: %s\n' "$pos" >&2
    exit 1
fi

set -- 31 28 31 30 31 30 31 31 30 31 30 31
shift "$(( pos - 1 ))"
printf '%s\n' "$1"

This first picks out the number from the command line and puts it into pos. Then it sets the positional parameters that you had in your array. By shifting pos - 1 elements off this array, we have the wanted number in $1.

This would work even if the list contained strings with spaces in them, such as in

#/bin/sh

pos=$1

if [ "$pos" -lt 1 ] || [ "$pos" -gt 12 ]; then
    printf 'No such month: %s\n' "$pos" >&2
    exit 1
fi

set -- "thirty one" "twenty eight" "thirty one" etc.
shift "$(( pos - 1 ))"
printf '%s\n' "$1"

The other way to solve this with /bin/sh is with a case statement:

case $1 in
    2)
        echo 28 ;;
    4|6|9|11)
        echo 30 ;;
    1|3|5|7|8|10|12)
        echo 31 ;;
    *)
        print 'No such month: %s\n' "$1" >&2
        exit 1
esac
Kusalananda
  • 14,885
  • 3
  • 41
  • 52
0

It's quite useful to use eval in this situation. You can simply define a couple of functions:

assign_element() { eval "__ARR_$1_$2=\"$3\""; }
get_element() { eval "echo \$__ARR_$1_$2"; }

You can then do:

assign_element days_of_week 1 31

and

$ get_element days_of_week 1
31

Of course, all this is really doing is creating separate variables whose names are of a fixed format relating to the array name and element. Depending on the situation you can make those variable names more complex to avoid clashes, and there's no reason for indices to be numeric. It's also trivial to assign a list of values to numeric indices using a small script to more closely match your original question, something like:

assign_list() {
    local N=0 NAME=$1
    while [ -n "$2" ] ; do
        assign_element "$NAME" $N "$2"
        shift
        N=$((N + 1))
    done
}

Then your initial problem becomes:

$ assign_list days_in_month 0 31 28 31 30 31 30 31 31 30 31 30 31
$ get_element days_in_month 1
31
Sidepipe
  • 89
  • 1
  • 2