echo and printf :: Bash

Intro

Note

For all the examples that follow, set the PS1 prompt to a simple $␠ (the dollar sign followed by a single whitespace) unless otherwise noted. E.g:

PS1='\$ '

echo

echo and newline

$ echo -n

Why does echo -n still produce a newline and the next prompt is on a line of its own?

It is not echo that is producing a newline. Because we first add a newline by hitting Enter (a.k.a Return) in order to execute the command, then bash prints nothing (we provided nothing for echo to print), the output is a newline from our Enter and nothing else. The $ prompt is positioned on a line of its own simply because echo had nothing to print.

When we do echo -n foo and hit Enter, we first produce a newline, then bash prints 'foo'.

$ echo -n foo<Return>
foo$

Then the prompt $ is positioned immediately after 'foo'. After all, we asked echo NOT to append a newline, so, echo prints 'foo' and the prompt is positioned right after `echo’s output.

How to print '-n'?

If we just do echo -n, the -n is treated as the -n option (do not append a newline).

This doesn’t work:

$ echo -- -n
-- -n.

Not what we want…​ We learned in End of Options that a anything following -- should be treated as a normal string operand, and not as an option to the program. Why then is -- not working here and preventing -n from beting treaded an option‽ Because bash’s echo honors the specs:

The echo utility shall not recognize the “--” argument in the manner specified by Guideline 10 of XBD Utility Syntax Guidelines; “--” shall be recognized as a string operand.

— echo POSIX spec

We can man ascii and look for the numeric value of --:

Excerpt from `man ascii’.

Oct   Dec   Hex   Char
──────────────────────
...
055   45    2D    -
...

Then we can use the -e option for echo and use the octal or hexadecimal values to produce - and just implicitly concatenate both - and n.

$ echo -e '\055'n
-n

$ echo -e '\x2d'n
-n

It has been said that:

"Any fool can make something complicated. It takes a genius to make it simple.”

Therefore:

$ echo -n -; echo n;

Jokes apart, the version with -e and \x2d is cool and useful too. It is nice to have the tools and know how to use them.

Prefer printf instead of echo

The use of echo is discouraged for several reasons. First, see echo application usage.

Basically, behaviour differs across implementations making it all but impossible to use echo in a reliable and portable way.

Also, observe the output of these commands:

$ var=-e
$ echo "$var"

Nothing is printed. 😮

$ arr=(-e -n -en -ne)
$ echo "${arr[@]}"

Same problem…​ But we are fine with printf:

$ var=-e
$ printf '%s\n' "$var"
-e
$ arr=(-e -n -en -ne)
$ printf '%s\n' "${arr[@]}"
-e
-n
-en
-ne

However, these work with echo:

$ var=-e
$ echo "hello $var"
hello -e

$ arr=(-e -n -en -ne)
$ printf 'hello %s\n' "${arr[@]}"
hello -e
hello -n
hello -en
hello -ne

As do these:

$ echo " $var"
 -e

$ printf ' %s\n' "${arr[@]}"
 -e
 -n
 -en
 -ne

In bash’s echo at least, we can print those option-like parameters as long as there is something before them. Even a whitespace before them causes it to work. But do note that the space is preserved in the output.

Well, the options are there, and echo can still be used for certain things, but care must be taken.

printf

Contrary to echo, printf does not add a newline by default.

$ printf '%s' hello
hello$

$ printf '%s\n' hello
hello
$

Format operand reutilization

Another thing to consider is that the format operand (%s, %d, etc.) is reused until all argument operands are consumed:

"The format operand shall be reused as often as necessary to satisfy the argument operands."

That explains why even with a single %s, the next line prints all argument operands (instead of just the first one):

$ printf '%s\n' may the force
may
the
force

$ words=(be with you)
$ printf '%s\n' "${words[@]}"
be
with
you