Search code examples
shellshbusyboxquotingash

How can I store a list of quoted strings in a variable in Bourne shell? (without Bash arrays)


Using Bash's arrays, I can store a list into an array variable and iterate over the elements while keeping quoted strings containing spaces or special characters together:

LIST=(--foo --bar --baz "test 1 2 3")
for item in "${LIST[@]}"; do
    echo "$item"
done

Output is:

--foo
--bar
--baz
test 1 2 3

I have a script that uses this feature and unfortunately need to port it to use Busybox's ash (which doesn't support arrays). I'm trying to figure out a good way to store a list in which some items may spaces, while still preserving the correct number of elements of the list.

This doesn't work, for instance (incorrectly splits test 1 2 3 into separate items):

LIST='--foo --bar --baz "test 1 2 3"'
for item in $LIST; do
    echo "$item"
done

Output is:

--foo
--bar
--baz
"test
1
2
3"

One idea I found on the Busybox mailing list is to use set -- to replace the positional parameters:

set -- --foo --bar --baz "test 1 2 3"
for item in "$@"; do
    echo "$item"
done

Output is correct:

--foo
--bar
--baz
test 1 2 3

However, this construction clobbers the positional argument list ($@), which this script also uses.

Is there any reasonable way I can have my cake and eat it too, and simulate multiple arbitrary arrays in non-Bash sh variant?


Solution

  • The reason most shells provide arrays of some kind is because you cannot safely simulate them using flat strings. If you can isolate the code that needs an array from the rest of your script, you can execute it in a subshell so that the positional parameters can be restored after the subshell exits.

    ( # Nothing in here can rely on the "real" positional
      # parameters, unless you can fsave them to a fixed set of indiviual
      # parameters first.
      first_arg=$1  # etc
      set -- --foo --bar --baz "test 1 2 3"
      for item in "$@"; do
          echo "$item"
      done
      # Any other parameters you set or update will likewise revert
      # to their former values now.
    )