rsalz@uunet.uu.net (Rich Salz) (02/09/89)
Submitted-by: Chris Torek <chris@mimsy.umd.edu> Posting-number: Volume 17, Issue 91 Archive-name: printf Printf duplicates (as far as possible) the standard C library routine of the same name, at the shell command level. It is similar to echo, except that it formats its arguments according to conversion specifications given in the format-string before writing them to the standard output. For a thorough explanation of format specifications, see printf(3s). It implements most of the ANSI specification, as well as Roman Numerals. -Chris : 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.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@mimsy.umd.edu> X.sp XChris Torek <chris@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 $made; then chmod 444 printf.1 echo -n ' '; ls -ld printf.1 fi echo Extracting printf.c sed 's/^X//' <<'//go.sysin dd *' >printf.c X#ifndef lint Xstatic char rcsid[]= "$Header: printf.c,v 2.3 88/08/19 03:41:12 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(); 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 * Return the index of the character c in the string s; character 0 X * is NOT considered part of the string (unlike index() or strchr()). X * If c is not found (or is 0), return -1. X * X * This is used for hex and octal digit conversions in ctrl(). X */ Xint Xdigit(s, c) X char *s; X register int c; X{ X register char *p; X X for (p = s; *p; p++) X if (*p == c) X return (p - s); X return (-1); 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 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 /* octal constant, 3 digits maximum */ X v = digit(oct, s[-1]); X if ((c = digit(oct, *s)) >= 0) { X v = (v << 3) + c; X if ((c = digit(oct, *++s)) >= 0) { X v = (v << 3) + c; X s++; X } X } X *op++ = v; X break; X X case 'x': /* hex constant */ X v = 0; X while ((c = digit(hex, *s)) >= 0) { X if (c >= 16) X c -= 6; X v = (v << 4) + 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` != 8021 ]; then made=false echo error transmitting printf.c -- echo length should be 8021, not `wc -c < printf.c` else made=true fi if $made; then chmod 444 printf.c echo -n ' '; ls -ld printf.c fi -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.