3

I'm trying to get the output of env in a shell variable and print it.

#!/bin/sh
ENV_BEFORE=$(env)
printf $ENV_BEFORE

As a result a single variable from the env output gets printed.

When using echo instead of printf all the output is printed, however without the newlines.

What am I missing here?

8

The problem is that you're not quoting the $ENV variable. As explained in man bash:

Enclosing characters in double quotes preserves the literal value of all characters within the quotes, with the exception of $, `, \, and, when history expansion is enabled, !. The characters $ and ` retain their special meaning within double quotes. The backslash retains its special meaning only when followed by one of the following characters: $, `, ", \, or .

So, enclosing a sequence like \n in double quotes preserves its meaning. This is why, when not quoted, \n is just a normal n:

$ printf \n
n$

While, when quoted:

$ printf "\n"

$

An unquoted variable in bash invokes the split+glob operator. This means that the variable is split on whitespace (or whatever the special variable $IFS has been set to) and each resulting word is used as a glob (it will expand to match any matching file names). Your problem is with the "split" part of this.

To illustrate, let's take a simpler multiline variable:

$ var=$(printf "foo\nbar\n")

Now, using the shell's set -x debug feature, you can see exactly what's going on:

$ echo $var
+ echo foo bar
foo bar

$ echo "$var"
+ echo 'foo
bar'
foo
bar

As you can see above, echo $var (unquoted) subjects $var to split+glob so it results in two separate strings, foo and bar. The newline was eaten by th split+glob. When the variable was quoted, it wasn't subjected to split+glob, the newline was kept and, because it is quoted, is also interpreted correctly ad printed out.

The next problem is that printf is not like echo. It doesn't just print anything you give it, it expects a format string. For example printf "color:%s" "green" will print color:green because the %s will be replaced with green.

It also ignores any input that can't fit into the format string it was given. So, if you run printf foo bar, printf will treat foo as its format string and bar as the variable it is supposed to format with it. Since there is no %s or equivalent to be replaced by bar, bar is ignored and foo alone is printed:

$ printf $var
+ printf foo bar
foo

That's what happened when you ran printf $ENV_BEFORE. Because the variable wasn't quoted, the split glob effectively replaced newlines with spaces, and printf only printed the first "word" it saw.

To do it correctly, use format strings, and always quote your variables:

printf '%s\n' "$ENV_BEFORE"
4

printf (unlike echo) does not print newline by default, you have to explicitly tell it:

printf "${ENV_BEFORE}\n"

Or better:

printf '%s\n' "$ENV_BEFORE"

After @don_crissti rightly pointed out in the comment and after i have read the question again, i think your case is different from above.

In your case, the problem is that you have not quoted the variable $ENV_BEFORE.

Here are the behaviors of printf and echo if you don't quote a variable:

  • printf:

    1. Without quote and any format specifier it will only print the first word

    2. With quote and without any format specifier, it would consider anything in the expansion having % as format specifier and could choke

    3. Without quote and with a format specifier, it will subject to word splitting and pathname expansion, hence would lead to unexpected result as any value of IFS would become a space in the output

    4. With quote and format specifier, you would get desired result i.e. no word splitting and pathname expansion will be performed on the expansion.

  • echo:

    1. Without quote the variable expansion will subject to word splitting and pathname expansion, hence you would not get the desired output

    2. With quote the variable expansion will not subject to word splitting and pathname expansion and hence you would get the desired output

  • It works with what you suggested, thanks. However I can't understand the syntax that made it happen. Because of the added \n in '%s\n'all the newlines in ENV_BEFORE will be taken into account? – TheMeaningfulEngineer Mar 14 '16 at 12:16
  • @Alan Check my edits please.. – heemayl Mar 14 '16 at 13:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.