v17i091: A printf program
Rich Salz
rsalz at uunet.uu.net
Thu Feb 9 08:29:23 AEST 1989
Submitted-by: Chris Torek <chris at 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 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 $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 at uunet.uu.net.
More information about the Comp.sources.unix
mailing list