echo -[ne] and escapes

Chris Torek chris at mimsy.UUCP
Wed Jun 15 21:50:07 AEST 1988


In article <8093 at brl-smoke.ARPA> gwyn at brl-smoke.ARPA (Doug Gwyn ) writes:
>What I have suggested is that ALL echo commands be given both -n support
>(for V7/BSD compatibility) and -e support a la V8/V9.  That would provide
>a migration path for shell scripts and allow reasonable default behavior
>for some future merged version of "echo" instead of escape mapping a la
>SysV.

What does POSIX say about echo?

As for myself, I use the `printf' command to get formatted/escaped
strings.

What, you say you have no printf command?

This one is largely compatible with the dpANS, although it relies on
the library printf to do the real work; if the library printf
misbehaves, so does this one.  It does not implement the (in my opinion
ill-advised) %n formats, nor for the obvious reason the %p format, nor
does it handle %L[eEfgG] and |long double|s, but it does correctly
handle %ld and %hd formats, which our previous printf command did not.
I also replaced Fred's simple and obvious Roman numeral algorithm with
a shorter, more devious one due to Knuth.  Deciphering it is left as an
exercise :-) .

Note that we added one backslash convention not in the dpANS, since
string concatenation is not available.

Incidentally, while this version of printf handles more formats, it
is shorter than the old one, and actually compiles to less code.

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo Extracting printf.c
sed 's/^X//' <<'//go.sysin dd *' >printf.c
X#ifndef lint
Xstatic char rcsid[]= "$Header: printf.c,v 2.2 88/06/15 07:29:48 chris Exp $";
X#endif
X
X/*
X * printf - duplicate the C library routine of the same name, but from
X * the shell command level.
X *
X * This version by Chris Torek, based on an earlier version by Fred Blonder.
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <sysexits.h>
X
Xchar	*progname;
X
Xchar	*ctor(), **doit(), *index();
Xdouble	atof();
Xint	atoi();
Xlong	atol();
X
Xmain(argc, argv)
X	int argc;
X	char **argv;
X{
X	register char *cp, *convp, **ap, **ep;
X	register int ch, ndyn, flags;
X	char cbuf[BUFSIZ];	/* separates each conversion */
X	static char hasmod[] = "has integer length modifier";
X
X	/* flags */
X#define	LONG	1
X#define	SHORT	2
X
X	ap = argv;
X	ep = &ap[argc];
X	progname = *ap++;
X	if (argc < 2) {
X		(void) fprintf(stderr,
X			"%s: Usage: %s <format-string> [ arg1 . . . ]\n",
X			progname, progname);
X		exit(EX_USAGE);
X	}
X
X	ctrl(cp = *ap++);	/* backslash interpretation of fmt string */
X
X	/*
X	 * Scan format string for conversion specifications.
X	 * (The labels would be loops, but then everything falls
X	 * off the right.)
X	 */
Xscan:
X	while ((ch = *cp++) != '%') {
X		if (ch == 0)
X			exit(EX_OK);
X		(void) putchar(ch);
X	}
X
X	ndyn = 0;
X	flags = 0;
X	convp = cbuf;
X	*convp++ = ch;
X
X	/* scan for conversion character */
Xcvt:
X	switch (ch = *cp++) {
X
X	case '\0':	/* unterminated conversion */
X		exit(EX_OK);
X
X	/* string or character format */
X	case 'c': case 's':
X		if (flags)
X			illfmt(cbuf, convp, ch, hasmod);
X		ap = doit(cbuf, convp, ap, ep, ndyn, ch, ch);
X		goto scan;
X
X	/* integer formats */
X	case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':
X		if ((flags & (LONG|SHORT)) == (LONG|SHORT))
X			illfmt(cbuf, convp, ch, "is both long and short");
X		ap = doit(cbuf, convp, ap, ep, ndyn, ch,
X			flags & LONG ? 'l' : flags & SHORT ? 'h' : 'i');
X		goto scan;
X
X	/* floating point formats */
X	case 'e': case 'E': case 'f': case 'g': case 'G':
X		if (flags)
X			illfmt(cbuf, convp, ch, hasmod);
X		ap = doit(cbuf, convp, ap, ep, ndyn, ch, 'f');
X		goto scan;
X
X	/* Roman (well, why not?) */
X	case 'r': case 'R':
X		if (flags)
X			illfmt(cbuf, convp, ch, hasmod);
X		ap = doit(cbuf, convp, ap, ep, ndyn, 's', ch);
X		goto scan;
X
X	case '%':	/* boring */
X		(void) putchar('%');
X		goto scan;
X
X	/* short integers */
X	case 'h':
X		flags |= SHORT;
X		break;
X
X	/* long integers */
X	case 'l':
X		flags |= LONG;
X		break;
X
X	/* field-width or precision specifier, or flag: keep scanning */
X	case '.': case '#': case '-': case '+': case ' ':
X	case '0': case '1': case '2': case '3': case '4':
X	case '5': case '6': case '7': case '8': case '9':
X		break;
X
X	/* dynamic field width or precision: count it */
X	case '*':
X		ndyn++;
X		break;
X
X	default:	/* something we cannot handle */
X		if (isascii(ch) && isprint(ch))
X			cbuf[0] = ch, cbuf[1] = 0;
X		else
X			(void) sprintf(cbuf, "\\%03o", (unsigned char)ch);
X		(void) fprintf(stderr,
X			"%s: illegal conversion character `%s'\n",
X			progname, cbuf);
X		exit(EX_USAGE);
X		/* NOTREACHED */
X	}
X
X	/* 2 leaves room for ultimate conversion char and for \0 */
X	if (convp >= &cbuf[sizeof(cbuf) - 2]) {
X		(void) fprintf(stderr, "%s: conversion string too long\n",
X			progname);
X		exit(EX_USAGE);
X	}
X	*convp++ = ch;
X	goto cvt;
X}
X
Xillfmt(cbuf, convp, ch, why)
X	char *cbuf, *convp;
X	int ch;
X	char *why;
X{
X
X	*convp++ = ch;
X	*convp = 0;
X	(void) fprintf(stderr, "%s: format `%s' illegal: %s\n",
X		progname, cbuf, why);
X	exit(EX_USAGE);
X}
X
X/*
X * Emit a conversion.  cch holds the printf format character for
X * this conversion; cty holds a simplified version (all integer
X * conversions, e.g., are represented as 'i').
X */
Xchar **
Xdoit(cbuf, convp, ap, ep, ndyn, cch, cty)
X	char *cbuf, *convp;
X	register char **ap;
X	char **ep;
X	register int ndyn;
X	int cch, cty;
X{
X	register char *s;
X	union {		/* four basic conversion types */
X		int i;
X		long l;
X		double d;
X		char *str;
X	} arg;
X	int a1, a2;	/* dynamic width and/or precision */
X
X	/* finish off the conversion string */
X	s = convp;
X	*s++ = cch;
X	*s = 0;
X	s = cbuf;
X
X	/* verify number of arguments */
X	if (&ap[ndyn] >= ep) {
X		(void) fprintf(stderr,
X			"%s: not enough args for format `%s'\n",
X			progname, s);
X		exit(EX_USAGE);
X	}
X
X	/* pick up dynamic specifiers */
X	if (ndyn) {
X		a1 = atoi(*ap++);
X		if (ndyn > 1)
X			a2 = atoi(*ap++);
X		if (ndyn > 2) {
X			(void) fprintf(stderr,
X				"%s: too many `*'s in `%s'\n",
X				progname, s);
X			exit(EX_USAGE);
X		}
X	}
X
X#define	PRINTF(what) \
X	if (ndyn == 0) \
X		(void) printf(s, what); \
X	else if (ndyn == 1) \
X		(void) printf(s, a1, what); \
X	else \
X		(void) printf(s, a1, a2, what);
X
X	/* emit the appropriate conversion */
X	switch (cty) {
X
X	/* string */
X	case 's':
X		ctrl(arg.str = *ap++);
X		goto string;
X
X	/* roman (much like string) */
X	case 'r': case 'R':
X		arg.str = ctor(atoi(*ap++), cty == 'R');
Xstring:
X		PRINTF(arg.str);
X		break;
X
X	/* floating point */
X	case 'f':
X		arg.d = atof(*ap++);
X		PRINTF(arg.d);
X		break;
X
X	/* character */
X	case 'c':
X		ctrl(*ap);
X		arg.i = *(*ap++);
X		goto integer;
X
X	/* short integer */
X	case 'h':
X		arg.i = (short) atoi(*ap++);
X		goto integer;
X
X	/* integer */
X	case 'i':
X		arg.i = atoi(*ap++);
Xinteger:
X		PRINTF(arg.i);
X		break;
X
X	/* long integer */
X	case 'l':
X		arg.l = atol(*ap++);
X		PRINTF(arg.l);
X		break;
X	}
X	return (ap);
X}
X
X/*
X * Convert backslash notation to control characters, in place.
X */
Xctrl(s)
X	register char *s;
X{
X	register char *op = s;
X	register int v, c;
X	char *p;
X	static char oct[] = "01234567";
X	static char hex[] = "0123456789abcdefABCDEF";
X
X	while ((c = *s++) != 0) {
X		if (c != '\\') {
X			*op++ = c;
X			continue;
X		}
X		switch (*s++) {
X		case '\0':	/* end-of-string: user goofed */
X			s--;
X			break;
X
X		case '\\':	/* backslash */
X			*op++ = '\\';
X			break;
X
X		case 'n':	/* newline */
X			*op++ = '\n';
X			break;
X
X		case 't':	/* horizontal tab */
X			*op++ = '\t';
X			break;
X
X		case 'r':	/* carriage-return */
X			*op++ = '\r';
X			break;
X
X		case 'f':	/* form-feed */
X			*op++ = '\f';
X			break;
X
X		case 'b':	/* backspace */
X			*op++ = '\b';
X			break;
X
X		case 'v':	/* vertical tab */
X			*op++ = '\13';
X			break;
X
X		case 'a':	/* WARNING! DANGER! DANGER! DANGER! */
X			*op++ = '\7';
X			break;
X
X		case '0': case '1': case '2': case '3':
X		case '4': case '5': case '6': case '7':
X			s--;
X			/* octal constant */
X			c = 3, v = 0;
X			while (--c >= 0 && index(oct, *s)) {
X				v <<= 3;
X				v += *s++ - '0';
X			}
X			*op++ = v;
X			break;
X
X		case 'x':	/* hex constant */
X			v = 0;
X			while ((p = index(hex, *s)) != NULL) {
X				if ((c = p - hex) >= 16)
X					c -= 6;
X				v <<= 4;
X				v += c;
X				s++;
X			}
X			*op++ = v;
X			break;
X
X		/*
X		 * The name of this object is taken from troff:
X		 * \z might be better, but this has a precedent.
X		 * It exists solely so that we can end a hex constant
X		 * which must be followed by a legal hex character.
X		 */
X		case '&':	/* special zero-width `character' */
X			break;
X
X		default:
X			*op++ = s[-1];
X		}
X	}
X	*op = '\0';
X}
X
X/*
X * Convert integer to Roman Numerals. (How have you survived without it?)
X */
Xchar *
Xctor(x, caps)
X	int x, caps;
X{
X	static char buf[BUFSIZ];
X	register char *outp = buf;
X	register unsigned n = x;
X	register int u, v;
X	register char *p, *q;
X
X	if ((int)n < 0) {
X		*outp++ = '-';
X		n = -n;
X	}
X	p = caps ? "M\2D\5C\2L\5X\2V\5I" : "m\2d\5c\2l\5x\2v\5i";
X	v = 1000;
X	if (n >= v * BUFSIZ / 2)	/* conservative */
X		return ("[abortive Roman numeral]");
X	for (;;) {
X		while (n >= v)
X			*outp++ = *p, n -= v;
X		if (n == 0)
X			break;
X		q = p + 1;
X		u = v / *q;
X		if (*q == 2)		/* magic */
X			u /= *(q += 2);
X		if (n + u >= v) {
X			*outp++ = *++q;
X			n += u;
X		} else {
X			p++;
X			v /= *p++;
X		}
X	}
X	*outp = 0;
X	return (buf);
X}
//go.sysin dd *
if [ `wc -c < printf.c` != 7586 ]; then
	made=FALSE
	echo error transmitting printf.c --
	echo length should be 7586, not `wc -c < printf.c`
else
	made=TRUE
fi
if [ $all = TRUE ]; then
	echo '	Changing owner to bin'
	chown bin printf.c
else
	echo '	Original owner was bin'
fi
if [ $made = TRUE ]; then
	chmod 444 printf.c
	echo -n '	'; ls -ld printf.c
fi
echo Extracting printf.1
sed 's/^X//' <<'//go.sysin dd *' >printf.1
X.\"	@(#)printf.1	8-Jan-1987
X.\"
X.TH PRINTF 1 "8-Jan-1987"
X.UC 4
X.SH NAME
Xprintf \- formatted output at shell command level
X.SH SYNOPSIS
X.B printf 
X.I format-string
X[
X.I arg1
X] [
X.I arg2
X] ...
X.SH DESCRIPTION
X.I Printf
Xduplicates (as far as possible) the standard C library routine of the
Xsame name, at the shell command level.  It is similar to
X.IR echo ,
Xexcept that it formats its arguments according to conversion specifications
Xgiven in the
X.I format-string
Xbefore writing them to the standard output.
XFor a thorough explanation of format specifications, see
X.IR printf (3s).
X.PP
XFor the sake of perversity,
X.I printf
Ximplements one format conversion
X.B not
Xsupported by the standard printf subroutine: the
X.I %r
Xand
X.IR %R
Xconversions, which print integers as Roman numerals.  The
Xfirst format produces lowercase, and the second uppercase.
X.PP
XAs a convenience, within the
X.I format-string
Xand any string or character arguments,
X.I printf
Xconverts ``backslash notation'' \- as defined in the
Xdraft proposed ANSI C Standard X3J11 \- into the
Xappropriate control characters.
XThe Standard provides for hexadecimal escapes as ``\ex1a2F3c4...'', in
Xwhich the only way to terminate the escape is with a non-hexadecimal
Xcharacter.  This is not always suitable outside the C language, so
X.I printf
Xprovides one additional escape,
X.BR \e& ,
Xwhich expands to nothing, but in so doing serves to terminate a
Xhexadecimal escape.
X.SH EXAMPLES
X.nf
X.na
X.ta 0.6i
X.sp 2
X% printf 'Today is %s the %d of %s.\en' Monday 1 April
XToday is Monday the 1 of April.
X.sp 3
X% printf 'Interesting Numbers\en\en\etPie: %*.*f\en\etFoo: %g\en' \e
X	6 4 3.14159265 42
XInteresting Numbers
X
X	Pie: 3.1416
X	Foo: 42
X.sp 3
X% printf '%s %d, %R\en' July 4 1776
XJuly 4, MDCCLXXVI
X.sp 3
X% printf 'in ASCII this prints dd: \ex64\e&d.\en' 
Xin ASCII this prints dd: dd.
X.sp 2
X.fi
X.ad
X.SH AUTHORS
XFred Blonder <fred at mimsy.umd.edu>
X.sp
XChris Torek <chris at mimsy.umd.edu>
X.SH "SEE ALSO"
Xecho(1), printf(3s)
X.SH BUGS
XThe Roman conversions are not strictly correct.
XZero produces no text;
Xvery large values give the complaint ``abortive Roman numeral''.
XNegative Roman numerals are printed with a leading minus sign.
XIt is unclear what the Romans did in such cases,
Xalthough zero could perhaps be written as ``nihil''.
XValues in the millions were sometimes written
Xusing an M with an overbar,
Xbut there is no bar-M character in ASCII.
X.sp
XThe ``%n'' conversion is unimplementable.
XThe number of characters written is not returned.
XLong double formats are not supported.
//go.sysin dd *
if [ `wc -c < printf.1` != 2521 ]; then
	made=FALSE
	echo error transmitting printf.1 --
	echo length should be 2521, not `wc -c < printf.1`
else
	made=TRUE
fi
if [ $all = TRUE ]; then
	echo '	Changing owner to bin'
	chown bin printf.1
else
	echo '	Original owner was bin'
fi
if [ $made = TRUE ]; then
	chmod 444 printf.1
	echo -n '	'; ls -ld printf.1
fi
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris at mimsy.umd.edu	Path:	uunet!mimsy!chris



More information about the Comp.unix.wizards mailing list