From: joshua stein Subject: Re: /bin/echo: add -e to handle escape sequences To: tech@openbsd.org Date: Wed, 25 Feb 2026 09:00:50 -0600 anyone? On Sat, 21 Feb 2026 at 18:54:52 -0600, joshua stein wrote: > 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..ba0b368d990 100644 > --- bin/echo/echo.c > +++ bin/echo/echo.c > @@ -30,29 +30,51 @@ > * SUCH DAMAGE. > */ > > +#include > #include > #include > #include > #include > > +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; > + for (++argv; argv[0] && *argv[0] == '-'; argv++) { > + for (p = *argv + 1; *p != '\0'; p++) { > + switch (*p) { > + case 'E': > + eflag = 0; > + break; > + case 'e': > + eflag = 1; > + break; > + case 'n': > + nflag = 1; > + break; > + default: > + eflag = nflag = 0; > + goto echoargs; > + } > + } > } > - 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 +83,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; > +} >