Ubuntu – bash: populating array with elements containing spaces and special characters

bashscriptsspecial characters

I want to be able to populate elements of an an array with arbitrary strings, i.e. strings that may contain \ and spaces for instance. I wrote this :

#!/bin/bash
function populate_array () {
    if [ "$#" -gt 0 ] ; then
        # Enter array w/ elements as argument of executable 
        array=($@)
        n=$#
    else
        # Invoke executable with no arg,, enter array element later
        read -p "Enter array elements separated by spaces: " -a array  
        n=${#array[@]} 
    fi
    printf "%d array elements \n" "$n"
} 

populate_array "$@"

while (("$n" > 0))   # while [ "$n" -gt 0 ] ALSO WORKS
do
    printf "%s \n" "${array[$n-1]}"
    n=$n-1
done
exit 0

The while block is just meant for the purpose of checking array elements.
The function is simple enough to work well for arguments that contain no space or \. Not otherwise.

Trying to enter arguments to the executable as:

#!> bash [scriptname] lkl1239 343.4l 3,344 (34) "lklk  lkl" lkaa\ lkc

I'd like to see 6 arguments:

lkl1239
343.4l 
3,344
(34)
lklk  lkl
lkaa lkc

Instead I get thrown:

  • For ( => bash: syntax error near unexpected token `34'
  • Space containing strings are interpreted as x+1 strings, where x is
    the number of non consecutive spaces neither at the beginning nor at
    the end of a string.
  • Bash ignores what comes after the first occurence of \

How is this done ?

Best Answer

What you're doing is tricky. The normal way is to avoid this and just pass the array values as arguments. In order to have both options, you would have to use eval:

#!/bin/bash
function populate_array () {
    if [ "$#" -gt 0 ] ; then
        # Enter array w/ elements as argument of executable 
        # Note the quotes, they are needed
        array=("$@");
        n=$#
    else
        # Invoke executable with no arg, enter array element later
        # Read a string instead of an array and use eval to make it
        # into an array. That way, you can use tricks like escaping
        # spaces. You also need the -r option to protect the backslashes
        # so that eval will see them. 
        read -r  -p "Enter array elements separated by spaces: " string
        eval array="( $(printf '%s\n' "$string") )"
        n=${#array[@]} 
    fi
    printf "%d array elements \n" "$n"
} 

populate_array "$@"

while (("$n" > 0))   # while [ "$n" -gt 0 ] ALSO WORKS
do
    printf "%s \n" "${array[$n-1]}"
    n=$n-1
done
exit 0

You still need to escape the parentheses if you pass the array values as an argument since ( ) are reserved characters for bash. With that caveat, the script above should work as you expect:

$ foo.sh lkl1239 343.4l 3,344 \(34\) "lklk  lkl" lkaa\ lkc
6 array elements 
lkaa lkc 
lklk  lkl 
(34) 
3,344 
343.4l 
lkl1239 

And

$ foo.sh
Enter array elements separated by spaces:  lkl1239 343.4l 3,344 \(34\) "lklk  lkl" lkaa\ lkc 
6 array elements 
lkaa lkc 
lklk  lkl 
(34) 
3,344 
343.4l 
lkl1239