5

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 ?

4 Answers 4

3

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 
3
  • +1 for your solution. It's complete and the else block eval follows the pattern : prompt> set -- A B C; n=2; eval new="$(echo \${$n})"; echo $new .... With hindsight it's easier of course. Thanks.
    – Cbhihe
    Jun 18, 2015 at 21:26
  • My problem is now slightly displaced. The example of strings I gave as argument was just that... an example. In reality I want something so robust that ANY string could be passed on as element of the array. I am perfectly willing to test them for spaces but not to escape every single POSIX special character, and by special I mean anything in !"[(\&)/$·'`@=*-+{[}]^% or a combination thereof. I don't think there is an easy solution for that, short of somehow detailing what's in the bag before passing it on to the array. It would be incredibly expensive to do for large sources. Opinions ?
    – Cbhihe
    Jun 18, 2015 at 21:42
  • 1
    @Cbhihe in that case, you should probably forget about using read and just take quoted arguments. I would also ask on Unix & Linux instead of here, there are more shell experts there. Alternatively use something other than space to separate your entries and potato the array by parsing the string. I don't see what the evil would choke on though, try with unsecured parentheses, doesn't that work?
    – terdon
    Jun 18, 2015 at 21:58
2

Just quote the $@ as you already correctly do in the function invocation:

array=("$@")

As man bash puts it:

"$@" is equivalent to "$1" "$2" ...

0
0

Special characters typically have to be escaped with backslash, like so:

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

The brackets are treated by the shell as metacharacters, hence need to be escaped. The backslash in lkaa\ lkc already escapes the space between lkaa and lkc, so those two strings are treated as one. Same as with cd /home/user/bin/NAME\ WITH\ SPACE

And from man bash:

   metacharacter
                  A character that, when unquoted, separates words.  One of the following:
                  |  & ; ( ) < > space tab
5
  • Does using single quotes work in this case? I read that Bash interprets things inside a single-quoted string as literals, rather than interpreting it. (I.E. 'foo foobar foo\bar\baz 66@SUDO' might be read as those three elements? The problem is single quotes though in an element...)
    – Thomas Ward
    Jun 18, 2015 at 14:32
  • @ThomasW. putting single quotes around the whole line like you showed, makes the script treat that whole line as one element, and yes, that makes it treat items as literals. So every single item has to be single-quoted Jun 18, 2015 at 14:35
  • Nope. want 6 array elts, not 7
    – Cbhihe
    Jun 18, 2015 at 18:45
  • Hm, without extra backslash in lkaa\ lkc that gives 6 Jun 18, 2015 at 18:51
  • I think that's because lkaa\ lkc serves as escape itself for the space between the two strings, so it turns into one string with space Jun 18, 2015 at 18:52
0

can you try by placing below at top of script

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

and

IFS=$SAVEIFS 

in bottom of script

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .