[net.sources] "printf" lookalike from shell-level

fred@gyre.UUCP (Fred Blonder) (01/10/87)

Here's a handy little program which works sort of like echo, except
that it does formatting in the manner of printf. In fact, it uses
printf to do its formatting, so you have the full functionality of
printf available at the shell level.

The only problem with it is that it will run only on stack machines,
and only some of them. It runs on VAXes. It doesn't run on Suns.
I haven't tested it on anything else. The reason for this strangeness
is that I didn't want to allow only a fixed number of arguments to
the printf call, so I had to push the arguments onto the stack
myself, rather than using the normal C calling mechanism.  This
results in some rather odd code.

I suppose someday someone will write this transportably, but in
the meantime you can amaze your friends by showing them the call
to printf with no arguments.

No makefile here, just ``cc printf.c -o printf''.

 . . . . . . . . . . . . . CUT ON DOTTED LINE . . . . . . . . . . . . .
: 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.AT 3
X.SH NAME
printf \- formatted print at shell command level
X.SH SYNOPSIS
X.B "printf <format-string>"
[
X.B arg1
] [
X.B arg2
] ...
X.SH DESCRIPTION
X.I Printf
duplicates \- as far as possible \- the standard C library routine of the
same name, at the shell command level. It is similar to
X.I echo,
except that it formats its arguments according to conversion specifications
given in the
X.B format-string,
before writing them to the standard output.
XFor a thorough explanation of format specifications, see the manual entry
for the printf subroutine.
X.PP
As a convenience, within the
X.I format-string
and any string or character arguments,
X.I printf
converts "backslash notation" \- as defined in the ANSII draft C
standard \- into the appropriate control characters.
X.SH EXAMPLES
X.nf
X.na
X.sp 2
% printf 'Today is %s the %d of %s.\\n' Monday 1 April
Today is Monday the 1 of April.
X.sp 3
% printf 'Interesting Numbers\\n\\n\\tPie: %*.*f\\n\\tFoo: %g\\n' \\
	6 4 3.1415927 42
Interesting Numbers

	Pie: 3.1416
	Foo: 42
X.sp 2
X.fi
X.ad
X.SH AUTHOR
XFred Blonder <fred@Mimsy.umd.edu>
X.SH "SEE ALSO"
echo(1), printf(3)

//go.sysin dd *
if [ `wc -c < printf.1` != 1169 ]; then
	made=FALSE
	echo error transmitting printf.1 --
	echo length should be 1169, not `wc -c < printf.1`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.1
	echo -n '	'; ls -ld printf.1
fi
echo Extracting printf.c
sed 's/^X//' <<'//go.sysin dd *' >printf.c
#ifndef lint
static char sccsid[] = "@(#)printf.c	(U of Maryland) FLB 6-Jan-1987";
static char RCSid[] = "@(#)$Header: printf.c,v 1.2 87/01/09 19:10:57 fred Exp $";
#endif

X/*
 * Printf - Duplicate the C library routine of the same name, but from
 *	    the shell command level.
 *
 * WARNING: Gross code. Extremely machine dependent. (Works on VAXen.)
 *
 * To Compile:
 %	cc -s -O printf.c -o printf
 *
 * $Log:	printf.c,v $
 * Revision 1.2  87/01/09  19:10:57  fred
 * Fixed bug in argument-count error-checking.
 * Changed backslash escapes within strings to correspond to ANSII C
 * 	draft standard.  (9-Jan-87 FLB)
 * 
 */

#include <stdio.h>
#include <sysexits.h>

X/****************************************************************************/

main(argc, argv)
int argc;
char *argv[];
{
register char *cp;
register int *argp, *ep;
register unsigned long *sp;
char *conv_args, *sbrk(), *index();
double atof();
int conv_arg_size;
register int *tp;

if (argc < 2) {
	fprintf(stderr,
		"printf: Usage: printf <format-string> [ arg1 . . . ]\n");
	exit(EX_USAGE);
	}

argp = (int *)&argv[2];	/* Point at first arg (if any) beyond format string. */
ep = (int *)&argv[argc];	/* Point beyond last arg. */

conv_args = sbrk(0);	/* Remember current break. */

ctrl(argv[1]);	/* Change backslash notation to control chars in fmt string. */

tp = (int *)sbrk(sizeof(char *));	/* Put fmt string on arg list. */
*tp = (int)argv[1];

X/* Scan format string for conversion specifications, and do appropriate
   conversion on the corresponding argument. */
for (cp = argv[1]; *cp; cp++) {

	/* Look for next conversion spec. */
	while (*cp && *cp != '%') {
		cp++;
		}

	if (!*cp)	/* % at end of string - error - ignore */
		break;

	for (cp++; *cp && *cp != '%' && (int)index("*.scdoxufeg0123456789", *cp);
									cp++) {

		if (argp >= ep) {
			fprintf(stderr, "printf: not enough args for format\n");
			exit(EX_USAGE);
			}

		switch (*cp) {	/* Field-width spec.: Keep scanning. */
			case '.': case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7': case '8':
			case '9':
				continue;

			case 's':	/* String: no conversion */
				ctrl((char *)*argp);
				tp = (int *)sbrk(sizeof(char *));
				*tp = *argp;
				argp++;
				goto out;

			case 'c':	/* Char: copy it */
				ctrl((char *)*argp);
				tp = (int *)sbrk(sizeof(int *));
				*(int *)tp = *(char *)*argp;
				argp++;
				goto out;

			case '*':	/* Dynamic field-width spec */
				tp = (int *)sbrk(sizeof(int));
				*tp = atoi((char *)*argp);
				argp++;
				continue;

			case 'd':	/* Integer */
			case 'o':
			case 'x':
			case 'u':
				tp = (int *)sbrk(sizeof(int));
				*tp = atoi((char *)*argp);
				argp++;
				goto out;

			case 'f':	/* Real */
			case 'e':
			case 'g':
				tp = (int *)sbrk(sizeof(double));
				*(double *)tp = atof((char *)*argp);
				argp++;
				goto out;
			}
		}
	out: ;
	}

X/* Gross-out time! Copy converted argument list onto the stack. */
if (!(sp = (unsigned long *)alloca(conv_arg_size = (sbrk(0) - conv_args)))) {
	fprintf(stderr, "printf: Can't allocate stack space.\n");
	exit(EX_OSERR);
	}
bcopy(conv_args, sp, conv_arg_size);

printf();	/* Yes, this really does work. */

exit(EX_OK);
}

X/****************************************************************************/

X/* Convert backslash notation to control characters, in place. */

ctrl(s)
register char *s;
{
register char *op;
static int val;

for (op = s; *s; s++)
	if (*s == '\\')
		switch (*++s) {
			case '\0':	/* End-of-string: user goofed */
				goto out;

			case '\\':	/* Backslash */
				*op++ = '\\';
				break;

			case 'n':	/* newline */
				*op++ = '\n';
				break;

			case 't':	/* horizontal tab */
				*op++ = '\t';
				break;

			case 'r':	/* carriage-return */
				*op++ = '\r';
				break;

			case 'f':	/* form-feed */
				*op++ = '\f';
				break;

			case 'b':	/* backspace */
				*op++ = '\b';
				break;

			case 'v':	/* vertical tab */
				*op++ = '\13';
				break;

			case 'a':	/* WARNING! DANGER! DANGER! DANGER! */
				*op++ = '\7';
				break;

			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
				{	/* octal constant */
				register int digits;

				val = 0;
				(void) sscanf(s, "%3o", &val);
				*op++ = val;
				for (digits = 3; s[1] &&
					(int)index("01234567", s[1])
					&& --digits > 0;
						s++);
				}
				break;

			case 'x':	/* hex constant */
				s++;
				{
				register int digits;

				val = 0;
				(void) sscanf(s, "%3x", &val);
				*op++ = val;
				for (digits = 3; *s && s[1] &&
					(int)index("0123456789abcdefABCDEF",
									s[1])
					&& --digits > 0;
						s++);
				}
				break;

			}
	else
		*op++ = *s;

out:

*op = '\0';
}

X/****************************************************************************/
//go.sysin dd *
if [ `wc -c < printf.c` != 4776 ]; then
	made=FALSE
	echo error transmitting printf.c --
	echo length should be 4776, not `wc -c < printf.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.c
	echo -n '	'; ls -ld printf.c
fi
 . . . . . . . . . . . . . CUT ON DOTTED LINE . . . . . . . . . . . . .
-- 
-----
				Fred Blonder (301) 454-7690
				seismo!mimsy!fred
				Fred@Mimsy.umd.edu , @Maryland.CSNet

fred@mimsy.UUCP (Fred Blonder) (01/10/87)

Here's a handy little program which works sort of like echo, except
that it does formatting in the manner of printf. In fact, it uses
printf to do its formatting, so you have the full functionality of
printf available at the shell level.

The only problem with it is that it will run only on stack machines,
and only some of them. It runs on VAXes. It doesn't run on Suns.
I haven't tested it on anything else. The reason for this strangeness
is that I didn't want to allow only a fixed number of arguments to
the printf call, so I had to push the arguments onto the stack
myself, rather than using the normal C calling mechanism.  This
results in some rather odd code.

I suppose someday someone will write this transportably, but in
the meantime you can amaze your friends by showing them the call
to printf with no arguments.

No makefile here, just ``cc printf.c -o printf''.

 . . . . . . . . . . . . . CUT ON DOTTED LINE . . . . . . . . . . . . .
: 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.AT 3
X.SH NAME
printf \- formatted print at shell command level
X.SH SYNOPSIS
X.B "printf <format-string>"
[
X.B arg1
] [
X.B arg2
] ...
X.SH DESCRIPTION
X.I Printf
duplicates \- as far as possible \- the standard C library routine of the
same name, at the shell command level. It is similar to
X.I echo,
except that it formats its arguments according to conversion specifications
given in the
X.B format-string,
before writing them to the standard output.
XFor a thorough explanation of format specifications, see the manual entry
for the printf subroutine.
X.PP
As a convenience, within the
X.I format-string
and any string or character arguments,
X.I printf
converts "backslash notation" \- as defined in the ANSII draft C
standard \- into the appropriate control characters.
X.SH EXAMPLES
X.nf
X.na
X.sp 2
% printf 'Today is %s the %d of %s.\\n' Monday 1 April
Today is Monday the 1 of April.
X.sp 3
% printf 'Interesting Numbers\\n\\n\\tPie: %*.*f\\n\\tFoo: %g\\n' \\
	6 4 3.1415927 42
Interesting Numbers

	Pie: 3.1416
	Foo: 42
X.sp 2
X.fi
X.ad
X.SH AUTHOR
XFred Blonder <fred@Mimsy.umd.edu>
X.SH "SEE ALSO"
echo(1), printf(3)

//go.sysin dd *
if [ `wc -c < printf.1` != 1169 ]; then
	made=FALSE
	echo error transmitting printf.1 --
	echo length should be 1169, not `wc -c < printf.1`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.1
	echo -n '	'; ls -ld printf.1
fi
echo Extracting printf.c
sed 's/^X//' <<'//go.sysin dd *' >printf.c
#ifndef lint
static char sccsid[] = "@(#)printf.c	(U of Maryland) FLB 6-Jan-1987";
static char RCSid[] = "@(#)$Header: printf.c,v 1.2 87/01/09 19:10:57 fred Exp $";
#endif

X/*
 * Printf - Duplicate the C library routine of the same name, but from
 *	    the shell command level.
 *
 * WARNING: Gross code. Extremely machine dependent. (Works on VAXen.)
 *
 * To Compile:
 %	cc -s -O printf.c -o printf
 *
 * $Log:	printf.c,v $
 * Revision 1.2  87/01/09  19:10:57  fred
 * Fixed bug in argument-count error-checking.
 * Changed backslash escapes within strings to correspond to ANSII C
 * 	draft standard.  (9-Jan-87 FLB)
 * 
 */

#include <stdio.h>
#include <sysexits.h>

X/****************************************************************************/

main(argc, argv)
int argc;
char *argv[];
{
register char *cp;
register int *argp, *ep;
register unsigned long *sp;
char *conv_args, *sbrk(), *index();
double atof();
int conv_arg_size;
register int *tp;

if (argc < 2) {
	fprintf(stderr,
		"printf: Usage: printf <format-string> [ arg1 . . . ]\n");
	exit(EX_USAGE);
	}

argp = (int *)&argv[2];	/* Point at first arg (if any) beyond format string. */
ep = (int *)&argv[argc];	/* Point beyond last arg. */

conv_args = sbrk(0);	/* Remember current break. */

ctrl(argv[1]);	/* Change backslash notation to control chars in fmt string. */

tp = (int *)sbrk(sizeof(char *));	/* Put fmt string on arg list. */
*tp = (int)argv[1];

X/* Scan format string for conversion specifications, and do appropriate
   conversion on the corresponding argument. */
for (cp = argv[1]; *cp; cp++) {

	/* Look for next conversion spec. */
	while (*cp && *cp != '%') {
		cp++;
		}

	if (!*cp)	/* % at end of string - error - ignore */
		break;

	for (cp++; *cp && *cp != '%' && (int)index("*.scdoxufeg0123456789", *cp);
									cp++) {

		if (argp >= ep) {
			fprintf(stderr, "printf: not enough args for format\n");
			exit(EX_USAGE);
			}

		switch (*cp) {	/* Field-width spec.: Keep scanning. */
			case '.': case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7': case '8':
			case '9':
				continue;

			case 's':	/* String: no conversion */
				ctrl((char *)*argp);
				tp = (int *)sbrk(sizeof(char *));
				*tp = *argp;
				argp++;
				goto out;

			case 'c':	/* Char: copy it */
				ctrl((char *)*argp);
				tp = (int *)sbrk(sizeof(int *));
				*(int *)tp = *(char *)*argp;
				argp++;
				goto out;

			case '*':	/* Dynamic field-width spec */
				tp = (int *)sbrk(sizeof(int));
				*tp = atoi((char *)*argp);
				argp++;
				continue;

			case 'd':	/* Integer */
			case 'o':
			case 'x':
			case 'u':
				tp = (int *)sbrk(sizeof(int));
				*tp = atoi((char *)*argp);
				argp++;
				goto out;

			case 'f':	/* Real */
			case 'e':
			case 'g':
				tp = (int *)sbrk(sizeof(double));
				*(double *)tp = atof((char *)*argp);
				argp++;
				goto out;
			}
		}
	out: ;
	}

X/* Gross-out time! Copy converted argument list onto the stack. */
if (!(sp = (unsigned long *)alloca(conv_arg_size = (sbrk(0) - conv_args)))) {
	fprintf(stderr, "printf: Can't allocate stack space.\n");
	exit(EX_OSERR);
	}
bcopy(conv_args, sp, conv_arg_size);

printf();	/* Yes, this really does work. */

exit(EX_OK);
}

X/****************************************************************************/

X/* Convert backslash notation to control characters, in place. */

ctrl(s)
register char *s;
{
register char *op;
static int val;

for (op = s; *s; s++)
	if (*s == '\\')
		switch (*++s) {
			case '\0':	/* End-of-string: user goofed */
				goto out;

			case '\\':	/* Backslash */
				*op++ = '\\';
				break;

			case 'n':	/* newline */
				*op++ = '\n';
				break;

			case 't':	/* horizontal tab */
				*op++ = '\t';
				break;

			case 'r':	/* carriage-return */
				*op++ = '\r';
				break;

			case 'f':	/* form-feed */
				*op++ = '\f';
				break;

			case 'b':	/* backspace */
				*op++ = '\b';
				break;

			case 'v':	/* vertical tab */
				*op++ = '\13';
				break;

			case 'a':	/* WARNING! DANGER! DANGER! DANGER! */
				*op++ = '\7';
				break;

			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
				{	/* octal constant */
				register int digits;

				val = 0;
				(void) sscanf(s, "%3o", &val);
				*op++ = val;
				for (digits = 3; s[1] &&
					(int)index("01234567", s[1])
					&& --digits > 0;
						s++);
				}
				break;

			case 'x':	/* hex constant */
				s++;
				{
				register int digits;

				val = 0;
				(void) sscanf(s, "%3x", &val);
				*op++ = val;
				for (digits = 3; *s && s[1] &&
					(int)index("0123456789abcdefABCDEF",
									s[1])
					&& --digits > 0;
						s++);
				}
				break;

			}
	else
		*op++ = *s;

out:

*op = '\0';
}

X/****************************************************************************/
//go.sysin dd *
if [ `wc -c < printf.c` != 4776 ]; then
	made=FALSE
	echo error transmitting printf.c --
	echo length should be 4776, not `wc -c < printf.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.c
	echo -n '	'; ls -ld printf.c
fi
 . . . . . . . . . . . . . CUT ON DOTTED LINE . . . . . . . . . . . . .
-- 
					Fred Blonder (301) 454-7690
					seismo!mimsy!fred
					Fred@Mimsy.umd.edu

black@masscomp.UUCP (Sam Black) (01/12/87)

In article <9@gyre.UUCP> fred@gyre.UUCP (Fred Blonder) writes:
>Here's a handy little program which works sort of like echo, except
>that it does formatting in the manner of printf. In fact, it uses
>printf to do its formatting, so you have the full functionality of
>printf available at the shell level.

You can do the same thing with AWK, and it works on all machines
that have AWK.  With C shell, do something like:

	alias printf "echo ' ' | awk '{ printf(\!*) }'"

You can probably define a function in Bourne shell to do the same
thing, too.

----------------------------------------------

	There are only two kinds of planes in the world:
	Fighters and targets.
				- 1Lt. Steven Brown, 63 TFTS

		...!{decvax,allegra,harvard,seismo,unc,ihnp4}!masscomp!black

----------------------------------------------

jvb@duke.UUCP (Jack V. Briner, Jr.) (01/14/87)

In a series of articles people have been working on how to get a printf
function for the shell.  I would like to refine Sam Black's so that it
avoids an unnecessary piped command.  Try:

     alias printf "awk 'BEGIN {printf(\!*); exit;}'"

This will have to be modified if your history character is a "!".  If your
history character is a "," you will have to be careful about its usage.