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 0The 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\ lkcI'd like to see 6 arguments:
lkl1239
343.4l
3,344
(34)
lklk lkl
lkaa lkcInstead 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
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 0You 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 Just quote the $@ as you already correctly do in the function invocation:
array=("$@")As man bash puts it:
0
"$@"is equivalent to"$1" "$2" ...
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 can you try by placing below at top of script
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")and
IFS=$SAVEIFS in bottom of script