Bash, iterate over the words in a variable with white spaces

98 Views Asked by At

I have the following script:

#!/bin/sh

fruits="apple \"orange juice\" banana"

IFS=" " read -a words <<< "$fruits"
for word in "${words[@]}"
do
    echo "Word: $word"
done

Which produces the following:

Word: apple
Word: "orange
Word: juice"
Word: banana

It splits the sentence "orange juice" into two words. I want to achieve the following output:

Word: apple
Word: orange juice
Word: banana

But I am unable to make it work as I want. What's the problem here?

The following works as expected:

#!/bin/sh

for word in apple "orange juice" banana
do
    echo "Word: $word"
done

So I was expecting that the array expansion with "${words[@]}" did the trick, but it doesn't work. Any idea? thanks!

2

There are 2 best solutions below

3
pmf On BEST ANSWER

Note: Your shebang #!/bin/sh indicates POSIX sh. This conflicts with the use of here-strings and arrays. You may want to change it to #!/bin/bash (in conformance with the tag you've selected for this question).

That said, the intended encoding of your input string seems to be compatible with how bash expects encoding the definition of an indexed array, only short of the parens around it. Whether other instances would do as well is not obvious from that single example, but it's worth giving the use of declare -a a try.

#!/bin/bash

fruits="apple \"orange juice\" banana"

declare -a words="($fruits)"
for word in "${words[@]}"
do
    echo "Word: $word"
done
Word: apple
Word: orange juice
Word: banana
1
jhnc On

When you ask bash to split a string on spaces that's what it will do. It has no idea that you only want some spaces treated as separators.


Because some people, when confronted with a problem, think “I know, I'll use regular expressions.”, here's a way to split your string using bash's =~ operator:

parse(){
    local -n in=$1 out=$2 b=BASH_REMATCH
    local s=$in a=()

    while [[ $s =~ \"([^\"]+)\"|([^[:space:]]+) ]]; do
        a+=("${b[1]}${b[2]}")
        s=${s#*"$b"}
    done
    out=("${a[@]}")
}

Use as:

fruits="apple \"orange juice\" banana"

parse fruits words

Then words contains the pieces:

$ declare -p words
declare -a words=([0]="apple" [1]="orange juice" [2]="banana")
$