v13i032: Lit, a "better" echo

Rich Salz rsalz at bbn.com
Wed Feb 10 10:21:43 AEST 1988


Submitted-by: "Richard A. O'Keefe" <quintus!ok at SUN.COM>
Posting-number: Volume 13, Issue 32
Archive-name: lit

[  This program is like echo, except that it understands C-style \
   escapes, reasonably writes field-type data, and a couple of
   other things.  I spliced a Makefile into the shar.  --r$  ]

There are problems with the built-in echo(1) command:
4.2BSD:		If the first argument happens to be -n, tough luck.
System V:	You can't switch off character escapes.
Mixed (SUN):	There are two versions of echo around, and a
		script may get either depending on the preference
		of who calls it.
The designers of echo(1) evidently thought that it was only useful
for writing messages.  However, to get filename generation in the
value assigned to a Bourne shell variable, it is necessary to write
	variable=`echo ?pattern*`
which doesn't quite work.  lit(1) provides everything that the
4.2BSD or System V echo(1) commands provides, and a little bit more.
In particular,
	variable=`lit -- ?pattern*`
works correctly, even if the file names matched start with - or
contain \ .

The following shell archive contains lit.1 and lit.c .

Just compile lit.c, there are no options to set.

#! /bin/sh
cat >Makefile <\SHAR_EOF
all:		lit lit.1
install:	all
	@echo install according to local conventions
lit:		lit.c
	$(CC) $(CFLAGS) -o lit lit.c
SHAR_EOF
cat >lit.1 <<'------ EOF ------'
.TH LIT 1 "5 December 1987"
.SH NAME
lit \- echo arguments
.SH SYNOPSIS
.B echo
[
.B \-n
]
.B
[
\-d\fIlist\fB
]
.B
[
\-e\fIchar\fB
]
.B
[
\-\-
]
[ 
.I argument \fB.\|.\|.\fP 
]
.SH DESCRIPTION
.IX "lit command"  ""  "\fLlit\fP \(em echo arguments"
.I lit
writes its arguments on the standard output, as specified by
the options.  Arguments are normally separated by blanks and
terminated by a new-line, and written literally.
.BR C -style
character escapes are available
.I if
you want them.
.PP
.I lit
is useful for writing messages in shell scripts,
and for feeding short inputs into programs (this is sometimes
clearer than using "here files").
.PP
.I lit
is a close relative of the 4.2 BSD command
.I echo
and the System V command
.IR echo .
Both versions of
.I echo
have serious design flaws which were avoided in the design of
.IR lit .
.SH OPTIONS
.IP \fB\-n\fP
Don't add the \fBn\fPewline to the output.
Note that the shell's back-quote mechanism strips a terminating
newline, so the only time you need this is when writing a prompt
to the terminal.
.IP \fB\-d\fPlist
Normally,
.I lit
inserts a space between its arguments.  You can over-ride this
with the \-d option.  With a bare \-d, the tab character is used.
With a list of characters, the characters are used circularly
(as in paste(1)).  For example, \-dxy will insert x after the
1st, 3rd, 5th &c arguments and y after the 2nd, 4th, 6th &c.
There is currently no way of specifying an empty or multi-
character separator, but see the examples.
.IP \fB\-e\fPchar
.I lit
normally copies its arguments to its standard output literally.
If you want
.BR C -style
character escapes, use this option.  A bare \-e specifies the
escape character to be \e as in C.  \-eX specifies the
escape character to be X.  You may find \-e@ to be useful in
shell script as a way of avoiding conflicts with the shell's
use of \e .  Alphabetic case is ignored in the escapes, which are
.PP
.RS
.PD 0
.TP
.B \ea
audible alarm (bell)
.TP
.B \eb
backspace
.TP
.B \ed
delete
.TP
.TP
.B \ee
escape (ESC, not \e)
.B \ef
form-feed
.TP
.B \en
new-line
.TP
.B \er
carriage return
.TP
.B \es
space
.TP
.B \et
tab
.TP
.B \ev
vertical tab
.TP
.B \e\e
the escape character itself
.TP
.BI \ex n
the 8-bit character whose \s-1ASCII\s0 code is
the 1- or 2-digit hexadecimal number
.IR n .
.TP
.BI \eo n
the 8-bit character whose \s-1ASCII\s0 code is
the 1-, 2- or 3-digit octal number
.IR n ,
which need not start with a zero.
.TP
.BI \e n
the 8-bit character whose \s-1ASCII\s0 code is
the 1-, 2- or 3-digit octal number
.IR n ,
which need not start with a zero.
.RE
.SH EXAMPLES
.TP 15m
lit \|\-n "Enter count: " ; read count
To obtain prompted input in the Bourne shell.
.TP
lit -n foo ; lit -n baz ; lit ugh
echo "foobazugh" to the terminal with no separators.
.TP
lit \|\-\- \-n \-e \-d
echo "-n -e -d" to the standard output.
.TP
lit \|\-e@ "a at nb@nc" \|\(bv\| program
run program with three lines of standard input.
.TP
variable=`lit \-d: \-\- *foo*`
to assign to the shell variable "variable" a colon-separated
list of file names matching *foo* .  The \-\- is needed in
case the file such file name starts with a hyphen.  Note that
you cannot do this with the System V
.I echo
command, because the file names might contain \e Characters, which 
.I echo
would (mis-)interpret.
.SH SEE ALSO
sh(1), echo(1)
------ EOF ------
ls -l lit.1
cat >lit.c <<'------ EOF ------'
/*  File   : lit.c
    Author : Richard A. O'Keefe
    Updated: 5 December 1987
    Purpose: Replacement for echo(1).

    There are two versions of the echo(1) command:
	4.2 BSD
	    echo [-n] argument...
        System V
	    echo argument...
    Both are badly designed.  The 4.2 version has no way of printing
    something which starts with -n.  The System V one has no way of
    switching off the escape character interpretation which is its
    great new feature, and lacks an equivalent of -n.

    The solution is to have a new command which does things RIGHT.

    SYNOPSIS
	lit [-n] [-dlist] [-echar] [--] argument...

    OPTIONS
    -n	suppress the new-line which is normally printed at the end.

    -dl	normally, a single space is written between the arguments.
	With the option "-d" on its own, a tab is written instead.
	When there are characters after the "-d", these characters
	are used in turn, e.g. -d": , " would use ":" then " " then
	"," then " " then ":" again.  The default is thus equivalent
	to -d" ".

    -ex	normally, the arguments are copied literally.
	With the option "-e" on its own, C-style character escapes
	are interpreted.  In fact, rather more than most Cs.
	With the option "-ex", the character "x" is used instead of
	C's backslash;  this makes it easier to use in scripts.
	For example,
	    lit -e@ a at tb@tc
	writes out a, TAB, b, TAB, c, NEWLINE.

    --	indicates the end of the options.  This is only needed if the
	first argument begins with a "-".
*/

#include <stdio.h>

char *program_name = "lit";

void OutputError()
    {
	(void) fprintf(stderr,
	    "%s: I/O error near byte %ld of output\n",
	    program_name, ftell(stdout));
	exit(1);
    }


void WriteChar(c)
    int c;
    {
	if (putchar(c) < 0) OutputError();
    }


void Display(string, escape_character)
    char *string;
    int escape_character;
    {
	register FILE *output = stdout;
	register char *s;
	register int c;
	int i, n;

	for (s = string; c = *s++; ) {
	    if (c == escape_character) switch (c = *s++) {
		case 'n': case 'N':		/* newline */
		    c = 10; break;
		case 't': case 'T':		/* tab */
		    c =  9; break;
		case 'r': case 'R':		/* return */
		    c = 13; break;
		case 'v': case 'V':		/* vertical tab */
		    c = 11; break;
		case 'b': case 'B':		/* backspace */
		    c =  8; break;
		case 'f': case 'F':		/* formfeed */
		    c = 12; break;
		case 'e': case 'E':		/* escape */
		    c = 27; break;
		case 'd': case 'D':		/* delete */
		    c =127; break;
		case 's': case 'S':		/* space */
		    c = 32; break;
		case 'a': case 'A':		/* alarm */
		    c =  7; break;
		case '^':			/* control */
		    c = *s++;
		    c = !c ? '^' : c == '?' ? 127 : c&31;
		    break;
		case 'x': case 'X':		/* hexadecimal */
		    for (i = 2, n = 0; --i >= 0; s++) {
			c = *s;
			if (c >= '0' && c <= '9') {
			    n = (n<<4) - '0' + c;
			} else
			if (c >= 'a' && c <= 'f') {
			    n = (n<<4) - ('a'-10) + c;
			} else
			if (c >= 'A' && c <= 'F') {
			    n = (n<<4) - ('A'-10) + c;
			} else {
			    break;
			}
		    }
		    c = n&255;
		    break;
		case 'o': case 'O':		/* octal */
		    c = *s++;
		    if (c < '0' || c > '7') {
			c = s[-1];
			break;
		    }
		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		    n = c-'0';
		    for (i = 2; --i >= 0 && (c = *s) >= '0' && c <= '7'; s++) {
			n = (n<<3) - '0' + c;
		    }
		    c = n&255;
		    break;
		default:
		    break;
	    }
	    if (putc(c, output) < 0) OutputError();
	}
    }


main(argc, argv)
    int argc;
    char **argv;
    {
	int escape_character = -1;	/* no such character */
	int write_trailing_newline = 1;	/* do it by default */
	int options_done = 0;		/* options not completed yet */
	char *separators = " ";		/* list of separators */
	char *next_separator = "";
	int argno;			/* argument number */

	if (argc > 0) program_name = argv[0];

	for (argno = 1; argno < argc && !options_done; argno++) {
	    if (argv[argno][0] != '-') {
		break;
	    } else
	    switch (argv[argno][1]) {
		case 'n': case 'N':	/* suppress trailing \n */
		    write_trailing_newline = 0;
		    break;
		case 'd': case 'D':	/* delimiters */
		    separators = argv[argno][2] ? argv[argno]+2 : "\t";
		    break;
		case 'e': case 'E':	/* escape */
		    escape_character = argv[argno][2];
		    if (!escape_character) escape_character = '\\';
		    break;
		case '-':		/* end of options */
		    options_done = 1;
		    break;
		default:
		    (void) fprintf(stderr,
			"%s: unknown option: %s\n",
			program_name, argv[argno]);
		    (void) fprintf(stderr,
			"Usage: lit [-n] [-dlist] [-echar] [--] arg...\n");
		    exit(1);
	    }
	}
	while (argno < argc) {
	    Display(argv[argno], escape_character);
	    argno++;
	    if (argno != argc) {
		if (!*next_separator) next_separator = separators;
		WriteChar(*next_separator++);
	    }
	}
	if (write_trailing_newline) WriteChar('\n');
	if (fflush(stdout) < 0) OutputError();
    }


------ EOF ------
ls -l lit.c
exit
-- 
For comp.sources.unix stuff, mail to sources at uunet.uu.net.



More information about the Comp.sources.unix mailing list