home *** CD-ROM | disk | FTP | other *** search
- Path: wugate!wucs1!uunet!bbn.com!rsalz
- From: rsalz@uunet.uu.net (Rich Salz)
- Newsgroups: comp.sources.unix
- Subject: v17i091: A printf program
- Message-ID: <1482@fig.bbn.com>
- Date: 8 Feb 89 22:29:23 GMT
- Lines: 565
- Approved: rsalz@uunet.UU.NET
-
- 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.
-