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 thatxrandr
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.