Skip to main content

Questions about either the shell builtin or `/bin/echo`. This tag should not be used if your question is simply about printing to the terminal, only apply it if your question is specific to the `echo` command.

echo prints its operands connected by space to stdout with a trailing new line, for example, echo hello world ..'OVERFLOW!' gives hello world ..OVERFLOW!.

The echo command — either program or builtin — may be one of the commands that new shell users are most familiar with, and may also be one of the commands that get built into most non-POSIX shells with similiar functionality (e.g. cmd, csh), and even got its name into other types of languages like PHP.

However, echo has its limitations. As in POSIX, echo generates undefined results when the first operand is -n, or any of the operands contains backslashes; while SUSv2 XCU explicitly defines -n to be treated as a string and backslashes to be escaped in a C-like way. There are also extra options extended by some implementations including GNU printf and bash, like -e to enable backslash interpretation and -E to explicitly disable it (default). With all the options, you should have already realized the consequences of echo "$var".

POSIX also makes this point clear:

It is not possible to use echo portably across all POSIX systems unless both -n (as the first argument) and escape sequences are omitted. [...]

New applications are encouraged to use printf instead of echo.

echo's printf replacement has been described in POSIX already, with examples for different echo variants. The following is an example with support for -e/-E/-n flags:

# Variant of http://www.etalabs.net/sh_tricks.html, mimics GNU & bash echo.
# with the addition that one can use echo - "$var" to output the content
# of $var verbatim (followed by a newline) like in zsh (but echo - still
# outputs - unlike in zsh).

echo() (
  fmt=%s end='\n' IFS=' '
  while [ $# -gt 1 ] ; do
    case "$1" in ([!-]*|-*[!neE]*) break;; esac # not a flag
    case "$1" in (*n*) end='';; esac # no newline
    case "$1" in (*e*) fmt=%b;; esac # interpret backslash escapes
    case "$1" in (*E*) fmt=%s;; esac # don't interpret backslashes
    shift
  done
  printf "$fmt$end" "$*"
)

The %b directive in printf works exactly as echo -e does: it only interprets \0ooo.

Read more about this: