Search code examples
bashsubstitutiondouble-quotesparameter-expansion

Bash parameter substitution fails on spaces and newlines


When I try to separate a string into its characters, it fails on spaces and newlines:

str=" random string
"
echo ${str//?/ \'&}
' 'r 'a 'n 'd 'o 'm ' 's 't 'r 'i 'n 'g '

The parameter substitution somehow ignores the spaces and newlines and interprets them as empty, is there a way to make it recognize them?


Solution

  • That's post-expansion word-splitting. If you quote the parameter expansion, you'll see all leading, interword, and trailing whitespace preserved.

    $ str=" random string
    "
    $ echo ${str//?/ \'&}
    ' 'r 'a 'n 'd 'o 'm ' 's 't 'r 'i 'n 'g '
    $ echo "${str//?/ \'&}"
     '  'r 'a 'n 'd 'o 'm '  's 't 'r 'i 'n 'g '
    
    $
    

    Technically, none of the spaces introduced by the substitution operator survive the unquoted parameter expansion; they simply become part of the whitespace used to separate arguments to echo, and echo re-inserts a single space between each of the arguments it writes to standard output. Compare:

    $ echo ${str//?/    \'&}
    ' 'r 'a 'n 'd 'o 'm ' 's 't 'r 'i 'n 'g '
    $ echo "${str//?/    \'&}"
        '     'r    'a    'n    'd    'o    'm    '     's    't    'r    'i    'n    'g    '
    
    

    If, based on one of your short-lived comments, you are trying to dynamically construct the equivalent of the command

    set -- ' ' 'r' 'a' 'n' 'd' 'o' 'm' ' ' 's' 't' 'r' 'i' 'n' 'g' '
    '
    

    I suggest simply iterating over the string and executing multiple set commands to build up the arguments.

    set --
    for ((i=0; i < ${#str}; i++)); do
        set -- "$@" "${str:i:1}"
    done