Download raw body.
/bin/echo: add -e to handle escape sequences
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 <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;
> + 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;
> +}
>
/bin/echo: add -e to handle escape sequences