Quoted vs unquoted string expansion

An unquoted variable (as in $var) or command substitution (as in $(cmd) or `cmd`) is the split+glob operator in Bourne-like shells.

That is, their content is split according to the current value of the $IFS special variable (which by default contains the space, tab and newline characters)

And then each word resulting of that splitting is subject to filename generation (also known as globbing or filename expansion), that is, they are considered as patterns and are expanded to the list of files that match that pattern.

So in for i in $(xrandr), the $(xrandr), because it's not within quotes, is split on sequences of space, tab and newline characters. And each word resulting of that splitting is checked for matching file names (or left as is if they don't match any file), and for loops over them all.

In for i in "$(xrandr)", we're not using the split+glob operator as the command substitution is quoted, so there's one pass in the loop on one value: the output of xrandr (without the trailing newline characters which command substitution strips).

However in echo $i, $i is unquoted again, so again the content of $i is split and subject to filename generation and those are passed as separate arguments to the echo command (and echo outputs its arguments separated by spaces).

So lesson learnt:

  • if you don't want word splitting or filename generation, always quote variable expansions and command substitutions
  • if you do want word splitting or filename generation, leave them unquoted but set $IFS accordingly and/or enable or disable filename generation if needed (set -f, set +f).

Typically, in your example above, if you want to loop over the blank separated list of words in the output of xrandr, you'd need to:

  • leave $IFS at its default value (or unset it) to split on blanks
  • Use set -f to disable filename generation unless you're sure that xrandr never outputs any * or ? or [ characters (which are wildcards used in filename generation patterns)

And then only use the split+glob operator (only leave command substitution or variable expansion unquoted) in the in part of the for loop:

set -f; unset -v IFS
for i in $(xrandr); do whatever with "$i"; done

If you want to loop over the (non-empty) lines of the xrandr output, you'd need to set $IFS to the newline character:

IFS='
'

A quoted newline is a newline. So echo "$1" gives a single command line argument to echo, which then prints the newlines directly.

An unquoted newline is whitespace. So echo $1 gives many command line arguments to echo, which prints them one after another separated with spaces.