On Wed, 18 Feb 2026 at 18:22:38 -0700, Theo de Raadt wrote:
> Can someone write the -e and -E diff for echo like I suggest please?
this makes cups-filters ippfind happy, running an mkr with it now
diff --git bin/echo/echo.1 bin/echo/echo.1
index 7286602d752..cfd0391eb44 100644
--- bin/echo/echo.1
+++ bin/echo/echo.1
@@ -41,7 +41,7 @@
.Nd write arguments to the standard output
.Sh SYNOPSIS
.Nm echo
-.Op Fl n
+.Op Fl Een
.Op Ar string ...
.Sh DESCRIPTION
The
@@ -63,6 +63,41 @@ is treated as part of
.Pp
The options are as follows:
.Bl -tag -width Ds
+.It Fl E
+Disable interpretation of backslash escape sequences (default).
+.It Fl e
+Enable interpretation of the following backslash escape sequences:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Cm \e\e
+A literal backslash.
+.It Cm \ea
+Alert (BEL).
+.It Cm \eb
+Backspace.
+.It Cm \ec
+Suppress further output, including the trailing newline character.
+.It Cm \ee
+Escape character.
+.It Cm \ef
+Form feed.
+.It Cm \en
+Newline.
+.It Cm \er
+Carriage return.
+.It Cm \et
+Horizontal tab.
+.It Cm \ev
+Vertical tab.
+.It Cm \e0 Ns Ar nnn
+The character whose octal value is
+.Ar nnn
+(zero to three octal digits).
+.It Cm \ex Ns Ar hh
+The character whose hexadecimal value is
+.Ar hh
+(one or two hexadecimal digits).
+.El
.It Fl n
Do not print the trailing newline character.
.El
@@ -79,17 +114,17 @@ utility is compliant with the
.St -p1003.1-2008
specification.
.Pp
-The flag
+The flags
+.Op Fl E ,
+.Op Fl e ,
+and
.Op Fl n
-conflicts with the behaviour mandated by the
+conflict with the behaviour mandated by the
X/Open System Interfaces option of the
.St -p1003.1-2008
specification,
-which says it should be treated as part of
+which says they should be treated as part of
.Ar string .
-Additionally,
-.Nm
-does not support any of the backslash character sequences mandated by XSI.
.Pp
.Nm
also exists as a built-in to
diff --git bin/echo/echo.c bin/echo/echo.c
index 52da05c050f..37e037e5d8c 100644
--- bin/echo/echo.c
+++ bin/echo/echo.c
@@ -30,29 +30,52 @@
* SUCH DAMAGE.
*/
+#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
+int escape(const char *);
+
int
main(int argc, char *argv[])
{
- int nflag;
+ int nflag = 0, eflag = 0;
+ const char *p;
if (pledge("stdio", NULL) == -1)
err(1, "pledge");
/* This utility may NOT do getopt(3) option parsing. */
- if (*++argv && !strcmp(*argv, "-n")) {
- ++argv;
- nflag = 1;
+ if (*++argv && **argv == '-' && (*argv)[1] != '\0') {
+ for (p = *argv + 1; *p != '\0'; p++)
+ if (*p != 'E' && *p != 'e' && *p != 'n')
+ goto echoargs;
+
+ for (p = *argv + 1; *p != '\0'; p++) {
+ switch (*p) {
+ case 'E':
+ eflag = 0;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ }
+ }
}
- else
- nflag = 0;
+echoargs:
while (*argv) {
- (void)fputs(*argv, stdout);
+ if (eflag) {
+ if (escape(*argv) != 0)
+ /* \c encountered */
+ return 0;
+ } else
+ (void)fputs(*argv, stdout);
if (*++argv)
putchar(' ');
}
@@ -61,3 +84,86 @@ main(int argc, char *argv[])
return 0;
}
+
+/* return -1 on \c to suppress further output */
+int
+escape(const char *s)
+{
+ int ch, n;
+
+ while ((ch = *s++) != '\0') {
+ if (ch != '\\') {
+ putchar(ch);
+ continue;
+ }
+
+ switch ((ch = *s++)) {
+ case '\0':
+ putchar('\\');
+ return 0;
+ case '\\':
+ putchar('\\');
+ break;
+ case 'a':
+ putchar('\a');
+ break;
+ case 'b':
+ putchar('\b');
+ break;
+ case 'c':
+ return -1;
+ case 'e':
+ putchar('\033');
+ break;
+ case 'f':
+ putchar('\f');
+ break;
+ case 'n':
+ putchar('\n');
+ break;
+ case 'r':
+ putchar('\r');
+ break;
+ case 't':
+ putchar('\t');
+ break;
+ case 'v':
+ putchar('\v');
+ break;
+ case '0':
+ /* octal: \0nnn */
+ ch = 0;
+ for (n = 0; n < 3 && *s >= '0' && *s <= '7'; n++)
+ ch = ch * 8 + (*s++ - '0');
+ putchar(ch);
+ break;
+ case 'x':
+ /* hexadecimal: \xhh */
+ if (isxdigit((unsigned char)*s)) {
+ ch = 0;
+ for (n = 0;
+ n < 2 && isxdigit(*s); n++) {
+ ch *= 16;
+ if (*s >= '0' && *s <= '9')
+ ch += *s - '0';
+ else if (*s >= 'a' && *s <= 'f')
+ ch += *s - 'a' + 10;
+ else
+ ch += *s - 'A' + 10;
+ s++;
+ }
+ putchar(ch);
+ } else {
+ putchar('\\');
+ putchar('x');
+ }
+ break;
+ default:
+ putchar('\\');
+ putchar(ch);
+ break;
+ }
+ }
+
+ return 0;
+}