home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume17 / printf < prev    next >
Encoding:
Internet Message Format  |  1989-02-08  |  12.2 KB

  1. Subject:  v17i091:  A printf program
  2. Newsgroups: comp.sources.unix
  3. Approved: rsalz@uunet.UU.NET
  4.  
  5. Submitted-by: Chris Torek <chris@mimsy.umd.edu>
  6. Posting-number: Volume 17, Issue 91
  7. Archive-name: printf
  8.  
  9. Printf duplicates (as far as possible) the standard C library routine of
  10. the same name, at the shell command level.  It is similar to echo, except
  11. that it formats its arguments according to conversion specifications given
  12. in the format-string before writing them to the standard output.  For a
  13. thorough explanation of format specifications, see printf(3s).
  14.  
  15. It implements most of the ANSI specification, as well as Roman Numerals.
  16.     -Chris
  17.  
  18. : Run this shell script with "sh" not "csh"
  19. PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
  20. export PATH
  21. all=false
  22. if [ x$1 = x-a ]; then
  23.     all=true
  24. fi
  25. echo Extracting printf.1
  26. sed 's/^X//' <<'//go.sysin dd *' >printf.1
  27. X.\"    @(#)printf.1    8-Jan-1987
  28. X.\"
  29. X.TH PRINTF 1 "8-Jan-1987"
  30. X.UC 4
  31. X.SH NAME
  32. Xprintf \- formatted output at shell command level
  33. X.SH SYNOPSIS
  34. X.B printf 
  35. X.I format-string
  36. X[
  37. X.I arg1
  38. X] [
  39. X.I arg2
  40. X] ...
  41. X.SH DESCRIPTION
  42. X.I Printf
  43. Xduplicates (as far as possible) the standard C library routine of the
  44. Xsame name, at the shell command level.  It is similar to
  45. X.IR echo ,
  46. Xexcept that it formats its arguments according to conversion specifications
  47. Xgiven in the
  48. X.I format-string
  49. Xbefore writing them to the standard output.
  50. XFor a thorough explanation of format specifications, see
  51. X.IR printf (3s).
  52. X.PP
  53. XFor the sake of perversity,
  54. X.I printf
  55. Ximplements one format conversion
  56. X.B not
  57. Xsupported by the standard printf subroutine: the
  58. X.I %r
  59. Xand
  60. X.IR %R
  61. Xconversions, which print integers as Roman numerals.  The
  62. Xfirst format produces lowercase, and the second uppercase.
  63. X.PP
  64. XAs a convenience, within the
  65. X.I format-string
  66. Xand any string or character arguments,
  67. X.I printf
  68. Xconverts ``backslash notation'' \- as defined in the
  69. Xdraft proposed ANSI C Standard X3J11 \- into the
  70. Xappropriate control characters.
  71. XThe Standard provides for hexadecimal escapes as ``\ex1a2F3c4...'', in
  72. Xwhich the only way to terminate the escape is with a non-hexadecimal
  73. Xcharacter.  This is not always suitable outside the C language, so
  74. X.I printf
  75. Xprovides one additional escape,
  76. X.BR \e& ,
  77. Xwhich expands to nothing, but in so doing serves to terminate a
  78. Xhexadecimal escape.
  79. X.SH EXAMPLES
  80. X.nf
  81. X.na
  82. X.ta 0.6i
  83. X.sp 2
  84. X% printf 'Today is %s the %d of %s.\en' Monday 1 April
  85. XToday is Monday the 1 of April.
  86. X.sp 3
  87. X% printf 'Interesting Numbers\en\en\etPie: %*.*f\en\etFoo: %g\en' \e
  88. X    6 4 3.14159265 42
  89. XInteresting Numbers
  90. X
  91. X    Pie: 3.1416
  92. X    Foo: 42
  93. X.sp 3
  94. X% printf '%s %d, %R\en' July 4 1776
  95. XJuly 4, MDCCLXXVI
  96. X.sp 3
  97. X% printf 'in ASCII this prints dd: \ex64\e&d.\en' 
  98. Xin ASCII this prints dd: dd.
  99. X.sp 2
  100. X.fi
  101. X.ad
  102. X.SH AUTHORS
  103. XFred Blonder <fred@mimsy.umd.edu>
  104. X.sp
  105. XChris Torek <chris@mimsy.umd.edu>
  106. X.SH "SEE ALSO"
  107. Xecho(1), printf(3s)
  108. X.SH BUGS
  109. XThe Roman conversions are not strictly correct.
  110. XZero produces no text;
  111. Xvery large values give the complaint ``abortive Roman numeral''.
  112. XNegative Roman numerals are printed with a leading minus sign.
  113. XIt is unclear what the Romans did in such cases,
  114. Xalthough zero could perhaps be written as ``nihil''.
  115. XValues in the millions were sometimes written
  116. Xusing an M with an overbar,
  117. Xbut there is no bar-M character in ASCII.
  118. X.sp
  119. XThe ``%n'' conversion is unimplementable.
  120. XThe number of characters written is not returned.
  121. XLong double formats are not supported.
  122. //go.sysin dd *
  123. if [ `wc -c < printf.1` != 2521 ]; then
  124.     made=false
  125.     echo error transmitting printf.1 --
  126.     echo length should be 2521, not `wc -c < printf.1`
  127. else
  128.     made=true
  129. fi
  130. if $made; then
  131.     chmod 444 printf.1
  132.     echo -n '    '; ls -ld printf.1
  133. fi
  134. echo Extracting printf.c
  135. sed 's/^X//' <<'//go.sysin dd *' >printf.c
  136. X#ifndef lint
  137. Xstatic char rcsid[]= "$Header: printf.c,v 2.3 88/08/19 03:41:12 chris Exp $";
  138. X#endif
  139. X
  140. X/*
  141. X * printf - duplicate the C library routine of the same name, but from
  142. X * the shell command level.
  143. X *
  144. X * This version by Chris Torek, based on an earlier version by Fred Blonder.
  145. X */
  146. X
  147. X#include <stdio.h>
  148. X#include <ctype.h>
  149. X#include <sysexits.h>
  150. X
  151. Xchar    *progname;
  152. X
  153. Xchar    *ctor(), **doit();
  154. Xdouble    atof();
  155. Xint    atoi();
  156. Xlong    atol();
  157. X
  158. Xmain(argc, argv)
  159. X    int argc;
  160. X    char **argv;
  161. X{
  162. X    register char *cp, *convp, **ap, **ep;
  163. X    register int ch, ndyn, flags;
  164. X    char cbuf[BUFSIZ];    /* separates each conversion */
  165. X    static char hasmod[] = "has integer length modifier";
  166. X
  167. X    /* flags */
  168. X#define    LONG    1
  169. X#define    SHORT    2
  170. X
  171. X    ap = argv;
  172. X    ep = &ap[argc];
  173. X    progname = *ap++;
  174. X    if (argc < 2) {
  175. X        (void) fprintf(stderr,
  176. X            "%s: Usage: %s <format-string> [ arg1 . . . ]\n",
  177. X            progname, progname);
  178. X        exit(EX_USAGE);
  179. X    }
  180. X
  181. X    ctrl(cp = *ap++);    /* backslash interpretation of fmt string */
  182. X
  183. X    /*
  184. X     * Scan format string for conversion specifications.
  185. X     * (The labels would be loops, but then everything falls
  186. X     * off the right.)
  187. X     */
  188. Xscan:
  189. X    while ((ch = *cp++) != '%') {
  190. X        if (ch == 0)
  191. X            exit(EX_OK);
  192. X        (void) putchar(ch);
  193. X    }
  194. X
  195. X    ndyn = 0;
  196. X    flags = 0;
  197. X    convp = cbuf;
  198. X    *convp++ = ch;
  199. X
  200. X    /* scan for conversion character */
  201. Xcvt:
  202. X    switch (ch = *cp++) {
  203. X
  204. X    case '\0':    /* unterminated conversion */
  205. X        exit(EX_OK);
  206. X
  207. X    /* string or character format */
  208. X    case 'c': case 's':
  209. X        if (flags)
  210. X            illfmt(cbuf, convp, ch, hasmod);
  211. X        ap = doit(cbuf, convp, ap, ep, ndyn, ch, ch);
  212. X        goto scan;
  213. X
  214. X    /* integer formats */
  215. X    case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':
  216. X        if ((flags & (LONG|SHORT)) == (LONG|SHORT))
  217. X            illfmt(cbuf, convp, ch, "is both long and short");
  218. X        ap = doit(cbuf, convp, ap, ep, ndyn, ch,
  219. X            flags & LONG ? 'l' : flags & SHORT ? 'h' : 'i');
  220. X        goto scan;
  221. X
  222. X    /* floating point formats */
  223. X    case 'e': case 'E': case 'f': case 'g': case 'G':
  224. X        if (flags)
  225. X            illfmt(cbuf, convp, ch, hasmod);
  226. X        ap = doit(cbuf, convp, ap, ep, ndyn, ch, 'f');
  227. X        goto scan;
  228. X
  229. X    /* Roman (well, why not?) */
  230. X    case 'r': case 'R':
  231. X        if (flags)
  232. X            illfmt(cbuf, convp, ch, hasmod);
  233. X        ap = doit(cbuf, convp, ap, ep, ndyn, 's', ch);
  234. X        goto scan;
  235. X
  236. X    case '%':    /* boring */
  237. X        (void) putchar('%');
  238. X        goto scan;
  239. X
  240. X    /* short integers */
  241. X    case 'h':
  242. X        flags |= SHORT;
  243. X        break;
  244. X
  245. X    /* long integers */
  246. X    case 'l':
  247. X        flags |= LONG;
  248. X        break;
  249. X
  250. X    /* field-width or precision specifier, or flag: keep scanning */
  251. X    case '.': case '#': case '-': case '+': case ' ':
  252. X    case '0': case '1': case '2': case '3': case '4':
  253. X    case '5': case '6': case '7': case '8': case '9':
  254. X        break;
  255. X
  256. X    /* dynamic field width or precision: count it */
  257. X    case '*':
  258. X        ndyn++;
  259. X        break;
  260. X
  261. X    default:    /* something we cannot handle */
  262. X        if (isascii(ch) && isprint(ch))
  263. X            cbuf[0] = ch, cbuf[1] = 0;
  264. X        else
  265. X            (void) sprintf(cbuf, "\\%03o", (unsigned char)ch);
  266. X        (void) fprintf(stderr,
  267. X            "%s: illegal conversion character `%s'\n",
  268. X            progname, cbuf);
  269. X        exit(EX_USAGE);
  270. X        /* NOTREACHED */
  271. X    }
  272. X
  273. X    /* 2 leaves room for ultimate conversion char and for \0 */
  274. X    if (convp >= &cbuf[sizeof(cbuf) - 2]) {
  275. X        (void) fprintf(stderr, "%s: conversion string too long\n",
  276. X            progname);
  277. X        exit(EX_USAGE);
  278. X    }
  279. X    *convp++ = ch;
  280. X    goto cvt;
  281. X}
  282. X
  283. Xillfmt(cbuf, convp, ch, why)
  284. X    char *cbuf, *convp;
  285. X    int ch;
  286. X    char *why;
  287. X{
  288. X
  289. X    *convp++ = ch;
  290. X    *convp = 0;
  291. X    (void) fprintf(stderr, "%s: format `%s' illegal: %s\n",
  292. X        progname, cbuf, why);
  293. X    exit(EX_USAGE);
  294. X}
  295. X
  296. X/*
  297. X * Emit a conversion.  cch holds the printf format character for
  298. X * this conversion; cty holds a simplified version (all integer
  299. X * conversions, e.g., are represented as 'i').
  300. X */
  301. Xchar **
  302. Xdoit(cbuf, convp, ap, ep, ndyn, cch, cty)
  303. X    char *cbuf, *convp;
  304. X    register char **ap;
  305. X    char **ep;
  306. X    register int ndyn;
  307. X    int cch, cty;
  308. X{
  309. X    register char *s;
  310. X    union {        /* four basic conversion types */
  311. X        int i;
  312. X        long l;
  313. X        double d;
  314. X        char *str;
  315. X    } arg;
  316. X    int a1, a2;    /* dynamic width and/or precision */
  317. X
  318. X    /* finish off the conversion string */
  319. X    s = convp;
  320. X    *s++ = cch;
  321. X    *s = 0;
  322. X    s = cbuf;
  323. X
  324. X    /* verify number of arguments */
  325. X    if (&ap[ndyn] >= ep) {
  326. X        (void) fprintf(stderr,
  327. X            "%s: not enough args for format `%s'\n",
  328. X            progname, s);
  329. X        exit(EX_USAGE);
  330. X    }
  331. X
  332. X    /* pick up dynamic specifiers */
  333. X    if (ndyn) {
  334. X        a1 = atoi(*ap++);
  335. X        if (ndyn > 1)
  336. X            a2 = atoi(*ap++);
  337. X        if (ndyn > 2) {
  338. X            (void) fprintf(stderr,
  339. X                "%s: too many `*'s in `%s'\n",
  340. X                progname, s);
  341. X            exit(EX_USAGE);
  342. X        }
  343. X    }
  344. X
  345. X#define    PRINTF(what) \
  346. X    if (ndyn == 0) \
  347. X        (void) printf(s, what); \
  348. X    else if (ndyn == 1) \
  349. X        (void) printf(s, a1, what); \
  350. X    else \
  351. X        (void) printf(s, a1, a2, what);
  352. X
  353. X    /* emit the appropriate conversion */
  354. X    switch (cty) {
  355. X
  356. X    /* string */
  357. X    case 's':
  358. X        ctrl(arg.str = *ap++);
  359. X        goto string;
  360. X
  361. X    /* roman (much like string) */
  362. X    case 'r': case 'R':
  363. X        arg.str = ctor(atoi(*ap++), cty == 'R');
  364. Xstring:
  365. X        PRINTF(arg.str);
  366. X        break;
  367. X
  368. X    /* floating point */
  369. X    case 'f':
  370. X        arg.d = atof(*ap++);
  371. X        PRINTF(arg.d);
  372. X        break;
  373. X
  374. X    /* character */
  375. X    case 'c':
  376. X        ctrl(*ap);
  377. X        arg.i = *(*ap++);
  378. X        goto integer;
  379. X
  380. X    /* short integer */
  381. X    case 'h':
  382. X        arg.i = (short) atoi(*ap++);
  383. X        goto integer;
  384. X
  385. X    /* integer */
  386. X    case 'i':
  387. X        arg.i = atoi(*ap++);
  388. Xinteger:
  389. X        PRINTF(arg.i);
  390. X        break;
  391. X
  392. X    /* long integer */
  393. X    case 'l':
  394. X        arg.l = atol(*ap++);
  395. X        PRINTF(arg.l);
  396. X        break;
  397. X    }
  398. X    return (ap);
  399. X}
  400. X
  401. X/*
  402. X * Return the index of the character c in the string s; character 0
  403. X * is NOT considered part of the string (unlike index() or strchr()).
  404. X * If c is not found (or is 0), return -1.
  405. X *
  406. X * This is used for hex and octal digit conversions in ctrl().
  407. X */
  408. Xint
  409. Xdigit(s, c)
  410. X    char *s;
  411. X    register int c;
  412. X{
  413. X    register char *p;
  414. X
  415. X    for (p = s; *p; p++)
  416. X        if (*p == c)
  417. X            return (p - s);
  418. X    return (-1);
  419. X}
  420. X
  421. X/*
  422. X * Convert backslash notation to control characters, in place.
  423. X */
  424. Xctrl(s)
  425. X    register char *s;
  426. X{
  427. X    register char *op = s;
  428. X    register int v, c;
  429. X    static char oct[] = "01234567";
  430. X    static char hex[] = "0123456789abcdefABCDEF";
  431. X
  432. X    while ((c = *s++) != 0) {
  433. X        if (c != '\\') {
  434. X            *op++ = c;
  435. X            continue;
  436. X        }
  437. X        switch (*s++) {
  438. X        case '\0':    /* end-of-string: user goofed */
  439. X            s--;
  440. X            break;
  441. X
  442. X        case '\\':    /* backslash */
  443. X            *op++ = '\\';
  444. X            break;
  445. X
  446. X        case 'n':    /* newline */
  447. X            *op++ = '\n';
  448. X            break;
  449. X
  450. X        case 't':    /* horizontal tab */
  451. X            *op++ = '\t';
  452. X            break;
  453. X
  454. X        case 'r':    /* carriage-return */
  455. X            *op++ = '\r';
  456. X            break;
  457. X
  458. X        case 'f':    /* form-feed */
  459. X            *op++ = '\f';
  460. X            break;
  461. X
  462. X        case 'b':    /* backspace */
  463. X            *op++ = '\b';
  464. X            break;
  465. X
  466. X        case 'v':    /* vertical tab */
  467. X            *op++ = '\13';
  468. X            break;
  469. X
  470. X        case 'a':    /* WARNING! DANGER! DANGER! DANGER! */
  471. X            *op++ = '\7';
  472. X            break;
  473. X
  474. X        case '0': case '1': case '2': case '3':
  475. X        case '4': case '5': case '6': case '7':
  476. X            /* octal constant, 3 digits maximum */
  477. X            v = digit(oct, s[-1]);
  478. X            if ((c = digit(oct, *s)) >= 0) {
  479. X                v = (v << 3) + c;
  480. X                if ((c = digit(oct, *++s)) >= 0) {
  481. X                    v = (v << 3) + c;
  482. X                    s++;
  483. X                }
  484. X            }
  485. X            *op++ = v;
  486. X            break;
  487. X
  488. X        case 'x':    /* hex constant */
  489. X            v = 0;
  490. X            while ((c = digit(hex, *s)) >= 0) {
  491. X                if (c >= 16)
  492. X                    c -= 6;
  493. X                v = (v << 4) + c;
  494. X                s++;
  495. X            }
  496. X            *op++ = v;
  497. X            break;
  498. X
  499. X        /*
  500. X         * The name of this object is taken from troff:
  501. X         * \z might be better, but this has a precedent.
  502. X         * It exists solely so that we can end a hex constant
  503. X         * which must be followed by a legal hex character.
  504. X         */
  505. X        case '&':    /* special zero-width `character' */
  506. X            break;
  507. X
  508. X        default:
  509. X            *op++ = s[-1];
  510. X        }
  511. X    }
  512. X    *op = '\0';
  513. X}
  514. X
  515. X/*
  516. X * Convert integer to Roman Numerals. (How have you survived without it?)
  517. X */
  518. Xchar *
  519. Xctor(x, caps)
  520. X    int x, caps;
  521. X{
  522. X    static char buf[BUFSIZ];
  523. X    register char *outp = buf;
  524. X    register unsigned n = x;
  525. X    register int u, v;
  526. X    register char *p, *q;
  527. X
  528. X    if ((int)n < 0) {
  529. X        *outp++ = '-';
  530. X        n = -n;
  531. X    }
  532. X    p = caps ? "M\2D\5C\2L\5X\2V\5I" : "m\2d\5c\2l\5x\2v\5i";
  533. X    v = 1000;
  534. X    if (n >= v * BUFSIZ / 2)    /* conservative */
  535. X        return ("[abortive Roman numeral]");
  536. X    for (;;) {
  537. X        while (n >= v)
  538. X            *outp++ = *p, n -= v;
  539. X        if (n == 0)
  540. X            break;
  541. X        q = p + 1;
  542. X        u = v / *q;
  543. X        if (*q == 2)        /* magic */
  544. X            u /= *(q += 2);
  545. X        if (n + u >= v) {
  546. X            *outp++ = *++q;
  547. X            n += u;
  548. X        } else {
  549. X            p++;
  550. X            v /= *p++;
  551. X        }
  552. X    }
  553. X    *outp = 0;
  554. X    return (buf);
  555. X}
  556. //go.sysin dd *
  557. if [ `wc -c < printf.c` != 8021 ]; then
  558.     made=false
  559.     echo error transmitting printf.c --
  560.     echo length should be 8021, not `wc -c < printf.c`
  561. else
  562.     made=true
  563. fi
  564. if $made; then
  565.     chmod 444 printf.c
  566.     echo -n '    '; ls -ld printf.c
  567. fi
  568.