Wednesday, December 15, 2010

Echo multiline variables with and without quotes

When working with multiline variables in a Bourne or Korn shell there are certain subtleties to using quotes which - if used careless - can give quite different results.
A multiline variable is a variable which contains two or more lines of text.
I will discuss for a given multiline variable $T the differences between
  • echo $T
  • echo "$T"
  • printf $T
  • printf "$T"
  • printf "%s" $T
  • printf "%s" "$T"
If you know which of these commands show the same result you can skip this page.

Example: assume your /etc/hosts looks like this
#
# Internet host table
#
127.0.0.1       localhost
10.6.102.3      comp-nis
10.6.129.146    foo.Bar.COM loghost
192.168.127.1   foo.Bar.COM-ce1 # DO NOT MODIFY
and your variable $T should contain all lines with the string 'host' in it ie. lines 2, 4 and 6.
% T=`grep host /etc/hosts`
echo $T displays all 3 lines in one line
% echo $T
# Internet host table 127.0.0.1 localhost 10.6.129.146 foo.Bar.COM loghost

echo "$T" displays all 3 lines as 3 lines ie. truly showing the results of the grep command
% echo "$T"
# Internet host table
127.0.0.1       localhost
10.6.129.146    foo.Bar.COM loghost

Since the use of echo is discouraged these days one needs to look at printf.
printf $T dislays only the first word in $T which happens to be just # in our case.
% printf $T
#%

printf "$T" shows all 3 lines but without the final newline
% printf "$T"
# Internet host table
127.0.0.1       localhost
10.6.129.146    foo.Bar.COM loghost%

printf "%s" $T shows all 3 lines in one line by removing all white space (blanks and newlines) i.e. also without the final newline
% printf "%s" $T
#Internethosttable127.0.0.1localhost10.6.129.146foo.Bar.COMloghost%

printf "%s" "$T" shows all 3 lines but without the final newline (same output as printf "$T")
% printf "%s" "$T"
# Internet host table
127.0.0.1       localhost
10.6.129.146    foo.Bar.COM loghost%
So out of all versions only echo "$T" did what one would expect and could be replaced by printf "$T\n" or printf "%s\n" "$T" ie. printf with a final newline.

The difference becomes important when you feed the contents of a variable to some command like U=`echo $T | some_cmd ...`

Assume you want to replace 'host' by 'HOST' in our example.
U=`echo "$T" | sed 's/host/HOST/'` is the right solution: 3 changed lines in $U
% echo "$U"
# Internet HOST table
127.0.0.1       localHOST
10.6.129.146    foo.Bar.COM logHOST
U=`echo $T | sed 's/host/HOST/'` will produce a one line output and only the first occurance of 'host' will be replaced
% echo "$U"
# Internet HOST table 127.0.0.1 localhost 10.6.129.146 foo.Bar.COM loghost
Sometimes though transferring a multiline output into a single line is what is needed so the difference in behaviour can be used to one's advantage.
Note that in csh there doesn't seem to be a way to preserve the multiline nature in a variable (at least I don't know of any).

No comments:

Post a Comment