I have heard that printf
is better than echo
. I can recall only one instance from my experience where I had to use printf
because echo
didn't work for feeding some text into some program on RHEL 5.8 but printf
did. But apparently, there are other differences, and I would like to inquire what they are as well as if there are specific cases when to use one vs the other.
Printf vs Echo – Why Printf is Better
echoprintftext processing
Best Answer
Basically, it's a portability (and reliability) issue.
Initially,
echo
didn't accept any option and didn't expand anything. All it was doing was outputting its arguments separated by a space character and terminated by a newline character.Now, someone thought it would be nice if we could do things like
echo "\n\t"
to output newline or tab characters, or have an option not to output the trailing newline character.They then thought harder but instead of adding that functionality to the shell (like
perl
where inside double quotes,\t
actually means a tab character), they added it toecho
.David Korn realized the mistake and introduced a new form of shell quotes:
$'...'
which was later copied bybash
andzsh
but it was far too late by that time.Now when a standard UNIX
echo
receives an argument which contains the two characters\
andt
, instead of outputting them, it outputs a tab character. And as soon as it sees\c
in an argument, it stops outputting (so the trailing newline is not output either).Other shells/Unix vendors/versions chose to do it differently: they added a
-e
option to expand escape sequences, and a-n
option to not output the trailing newline. Some have a-E
to disable escape sequences, some have-n
but not-e
, the list of escape sequences supported by oneecho
implementation is not necessarily the same as supported by another.Sven Mascheck has a nice page that shows the extent of the problem.
On those
echo
implementations that support options, there's generally no support of a--
to mark the end of options (theecho
builtin of some non-Bourne-like shells do, and zsh supports-
for that though), so for instance, it's difficult to output"-n"
withecho
in many shells.On some shells like
bash
¹ orksh93
² oryash
($ECHO_STYLE
variable), the behaviour even depends on how the shell was compiled or the environment (GNUecho
's behaviour will also change if$POSIXLY_CORRECT
is in the environment and with the version4,zsh
's with itsbsd_echo
option, some pdksh-based with theirposix
option or whether they're called assh
or not). So twobash
echo
s, even from the same version ofbash
are not guaranteed to behave the same.POSIX says: if the first argument is
-n
or any argument contains backslashes, then the behaviour is unspecified.bash
echo in that regard is not POSIX in that for instanceecho -e
is not outputting-e<newline>
as POSIX requires. The UNIX specification is stricter, it prohibits-n
and requires expansion of some escape sequences including the\c
one to stop outputting.Those specifications don't really come to the rescue here given that many implementations are not compliant. Even some certified systems like macOS5 are not compliant.
To really represent the current reality, POSIX should actually say: if the first argument matches the
^-([eEn]*|-help|-version)$
extended regexp or any argument contains backslashes (or characters whose encoding contains the encoding of the backslash character likeα
in locales using the BIG5 charset), then the behaviour is unspecified.All in all, you don't know what
echo "$var"
will output unless you can make sure that$var
doesn't contain backslash characters and doesn't start with-
. The POSIX specification actually does tell us to useprintf
instead in that case.So what that means is that you can't use
echo
to display uncontrolled data. In other words, if you're writing a script and it is taking external input (from the user as arguments, or file names from the file system...), you can't useecho
to display it.This is OK:
This is not:
(Though it will work OK with some (non UNIX compliant)
echo
implementations likebash
's when thexpg_echo
option has not been enabled in one way or another like at compilation time or via the environment).file=$(echo "$var" | tr ' ' _)
is not OK in most implementations (exceptions beingyash
withECHO_STYLE=raw
(with the caveat thatyash
's variables can't hold arbitrary sequences of bytes so not arbitrary file names) andzsh
'secho -E - "$var"
6).printf
, on the other hand is more reliable, at least when it's limited to the basic usage ofecho
.Will output the content of
$var
followed by a newline character regardless of what character it may contain.Will output it without the trailing newline character.
Now, there also are differences between
printf
implementations. There's a core of features that is specified by POSIX, but then there are a lot of extensions. For instance, some support a%q
to quote the arguments but how it's done varies from shell to shell, some support\uxxxx
for unicode characters. The behavior varies forprintf '%10s\n' "$var"
in multi-byte locales, there are at least three different outcomes forprintf %b '\123'
But in the end, if you stick to the POSIX feature set of
printf
and don't try doing anything too fancy with it, you're out of trouble.But remember the first argument is the format, so shouldn't contain variable/uncontrolled data.
A more reliable
echo
can be implemented usingprintf
, like:The subshell (which implies spawning an extra process in most shell implementations) can be avoided using
local IFS
with many shells, or by writing it like:Notes
1. how
bash
'secho
behaviour can be altered.With
bash
, at run time, there are two things that control the behaviour ofecho
(besideenable -n echo
or redefiningecho
as a function or alias): thexpg_echo
bash
option and whetherbash
is in posix mode.posix
mode can be enabled ifbash
is called assh
or ifPOSIXLY_CORRECT
is in the environment or with the theposix
option:Default behaviour on most systems:
xpg_echo
expands sequences as UNIX requires:It still honours
-n
and-e
(and-E
):With
xpg_echo
and POSIX mode:This time,
bash
is both POSIX and UNIX conformant. Note that in POSIX mode,bash
is still not POSIX conformant as it doesn't output-e
in:The default values for xpg_echo and posix can be defined at compilation time with the
--enable-xpg-echo-default
and--enable-strict-posix-default
options to theconfigure
script. That's typically what recent versions of OS/X do to build their/bin/sh
.No Unix/Linux implementation/distribution in their right mind would typically do that for. Actually, that's not true, the/bin/bash
though/bin/bash
that Oracle ships with Solaris 11 (in an optional package) seems to be built with--enable-xpg-echo-default
(that was not the case in Solaris 10).2. How
ksh93
'secho
behaviour can be altered.In
ksh93
, whetherecho
expands escape sequences or not and recognises options depends on the content of the$PATH
and/or$_AST_FEATURES
environment variables.If
$PATH
contains a component that contains/5bin
or/xpg
before the/bin
or/usr/bin
component then it behave the SysV/UNIX way (expands sequences, doesn't accept options). If it finds/ucb
or/bsd
first or if$_AST_FEATURES
7 containsUNIVERSE = ucb
, then it behaves the BSD3 way (-e
to enable expansion, recognises-n
).The default is system dependant, BSD on Debian (see the output of
builtin getconf; getconf UNIVERSE
in recent versions of ksh93):3. BSD for echo -e?
The reference to BSD for the handling of the
-e
option is a bit misleading here. Most of those different and incompatibleecho
behaviours were all introduced at AT&T:\n
,\0ooo
,\c
in Programmer's Work Bench UNIX (based on Unix V6), and the rest (\b
,\r
...) in Unix System IIIRef.-n
in Unix V7 (by Dennis RitchieRef)-e
in Unix V8 (by Dennis RitchieRef)-E
itself possibly initially came frombash
(CWRU/CWRU.chlog in version 1.13.5 mentions Brian Fox adding it on 1992-10-18, GNUecho
copying it shortly after in sh-utils-1.8 released 10 days later)While the
echo
builtin of thesh
of BSDs have supported-e
since the day they started using the Almquist shell for it in the early 90s, the standaloneecho
utility to this day doesn't support it there (FreeBSDecho
still doesn't support-e
, though it does support-n
like Unix V7 (and also\c
but only at the end of the last argument)).The handling of
-e
was added toksh93
'secho
when in the BSD universe in the ksh93r version released in 2006 and can be disabled at compilation time.4. GNU echo change of behaviour in 8.31
Since coreutils 8.31 (and this commit), GNU
echo
now expands escape sequences by default when POSIXLY_CORRECT is in the environment, to match the behaviour ofbash -o posix -O xpg_echo
'secho
builtin (see bug report).5. macOS
echo
Most versions of macOS have received UNIX certification from the OpenGroup.
Their
sh
builtinecho
is compliant as it'sbash
(a very old version) built withxpg_echo
enabled by default, but their stand-aloneecho
utility is not.env echo -n
outputs nothing instead of-n<newline>
,env echo '\n'
outputs\n<newline>
instead of<newline><newline>
.That
/bin/echo
is the one from FreeBSD which suppresses newline output if the first argument is-n
or (since 1995) if the last argument ends in\c
, but doesn't support any other backslash sequences required by UNIX, not even\\
.6.
echo
implementations that can output arbitrary data verbatimStrictly speaking, you could also count that FreeBSD/macOS
/bin/echo
above (not their shell'secho
builtin) wherezsh
'secho -E - "$var"
oryash
'sECHO_STYLE=raw echo "$var"
(printf '%s\n' "$var"
) could be written:And
zsh
'secho -nE - "$var"
(printf %s "$var"
) could be writtenImplementations that support
-E
and-n
(or can be configured to) can also do:For the equivalent of
printf '%s\n' "$var"
.7.
_AST_FEATURES
and the ASTUNIVERSE
The
_AST_FEATURES
is not meant to be manipulated directly, it is used to propagate AST configuration settings across command execution. The configuration is meant to be done via the (undocumented)astgetconf()
API. Insideksh93
, thegetconf
builtin (enabled withbuiltin getconf
or by invokingcommand /opt/ast/bin/getconf
) is the interface toastgetconf()
For instance, you'd do
builtin getconf; getconf UNIVERSE = att
to change theUNIVERSE
setting toatt
(causingecho
to behave the SysV way among other things). After doing that, you'll notice the$_AST_FEATURES
environment variable containsUNIVERSE = att
.