Copyright © 2014,2015 by Thomas E. Dickey

ECHO versus Portability


The X/Open documentation is a consensus based on documentation and the contributor's experience. Often it lacks precision. Sometimes there is no consensus. The echo command is an example.

Referring to IEEE Std 1003.1, 2004 Edition, Under OPTIONS:

and under OPERANDS:

Some readers do not notice that the two statements in OPERANDS are mutually exclusive. The standard only describes the behavior of “XSI-conformant” systems. Other possibilities are alluded to, but not specified.

Reading between the lines, there are some comments about the non-conformant systems:


The differences between conformant and non-conformant systems are interesting for autoconf scripts because the generated scripts try to show the beginning and end of

on a single line.

There are historically two main implementations of echo to consider:

Doing that using a shell script would involve checking for which feature was supported, and setting shell variables to reuse the test results.

Here is a short history of how this shell feature evolved in autoconf (also see repository):

In short, the feature was added early in autoconf's development, existed unchanged for eleven years before it was changed for cosmetic reasons. There was no functional requirement for this change; there were no shells which failed to produce the expected result.


Referring to config.status files which I capture as part of my build process (see the save-XXX scripts), I can see that the usual case is as I stated above:

More to the point, none of the systems for which I have data would have exercised the case mentioned by Kasal. He did not cite a configuration for which this would occur; it may have been hypothetical.

However, one of the nice features about standards is that people can misread them, to find whatever they want the standard to say. My attention was drawn to this recently by Gentoo bug report #531244. That report points to a Gentoo patch for dash, which (see comment #15):

See patch [1] which was applied to dash- in Gentoo. It changes echo
behavior as follows:
- disable interpretation of escape sequences;
- drop support for '-n' option.

So the following shell code doesn't work in any way with that version of dash (and
it is really not portable):
echo $ECHO_N "Some text $ECHO_C"

autoconf >=2.62 uses AS_ECHO macro which works fine.

Actually, "portable" is something different from the ability to work around defective software. That is exactly what happens when one applies the patch mentioned. In particular, note that the reason for the bug report was that the ECHO_C variable was a newline, which broke the sed script in config.status. None of the platforms to which any of my programs have been ported in the last fifteen years produced that result. Otherwise, there would have been a build failure—and bug report.

The patch cites two other Gentoo bug reports:

The relevant part of the patch removes the interpretation of escape sequences from the “echo” command (as well as “-n”). As noted in comment #3 of the more recent report, upstream rejected the patch. Gentoo went ahead and tried to use it, echoing the comment in the patch about size/speed.

Because most of the logic dealing with escapes in dash is shared by echo and printf, there is little possible saving for size. Likewise, the comment about speed is pointless (it only slows dash down when there are escapes to process).

Gentoo might fix the bug which was introduced into dash. I put a workaround in autoconf-252, which coincidentally gives a similar result to the mainstream version:

--- acspecific.m4       2010/08/14 22:07:16     1.5
+++ acspecific.m4       2014/12/04 01:28:50     1.6
@@ -65,9 +65,9 @@
 # Idea borrowed from dist 3.0.  Use `*c*,', not `*c,' because if `\c'
 # failed there is also a new-line to match.
-[case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
-  *c*,-n*) ECHO_N= ECHO_C='
-' ECHO_T='     ' ;;
+[case `echo "testing\c" 2>/dev/null; echo 1,2,3`,`echo -n testing 2>/dev/null; echo 1,2,3` in
+  *c*,-n*) ECHO_N= ECHO_C=     # newlines do not sed ;-) only broken shells would use this case anyway
+                  ECHO_T='     ' ;;
   *c*,*  ) ECHO_N=-n ECHO_C= ECHO_T= ;;
   *)      ECHO_N= ECHO_C='\c' ECHO_T= ;;