From:	MERC::"uunet!CRVAX.SRI.COM!RELAY-INFO-VAX" 20-JUN-1992 11:00:24.60
To:	INFO-VAX@KL.SRI.COM
CC:	
Subj:	re: VMS version of wc?

	Does anybody know of a VMS version of the Unix wc command? 

You could certainly have written one in the time it will take for this message
to circulate, but what the hell, enclosed below is the wc from the DECUS C
distribution.  The fwild and getredirection sources are included after the
main program.
							-- Jerry

----------------wc.c----------------
/*
 * wc [file ...]
 */

/*)BUILD	$(TKBOPTIONS) = {
			TASK	= ...WCX
		}
*/

#ifdef	DOCUMENTATION

title	wc	Word Count
index		Count Words, Lines, and Bytes in Files

synopsis

	wc [-options] [filename ...]

description

	Count the number of bytes, words, and lines in one or more files.
	wc accepts wild-card file name arguments.

	The following options are defined:

	.lm +8;
 -b	Count bytes in the input files.
 -c	Count characters in the input files - synonym for -b.
 -l	Count lines in the input files.
 -u	Unix-style output; omits the descriptions of the counts (e.g., the
	"bytes" in "5 bytes").
 -w	Count words in the input files.
	.lm -8;

	A word is defined as "optional spaces or tabs" followed by text up to
	the first space, tab, or end of line.

	If no options are given, wc provides all three counts.

	If no file names are given, wc reads from standard input.  All output
	is written to standard output.
diagnostics

	.lm +8
	.s.i -8;"file name": cannot open
	.s.i -8;"file name": illegal file name
	.lm -8

author

	Martin Minow

bugs

#endif
/*
 * Edit history
 * 0.0	??-???-?? MM	Creation
 * 1.0	29-May-85 JSL	Added VMS redirection code, all options, on-line
 *			help.
 */

char	*documentation[]  = {
"wc reads one or more files, counting characters, words and lines.",
"Usage:  wc [-bcluw] [filename ...]",
"",
"Where:",
"",
" -b	Count bytes in the input files.",
" -c	Count characters in the input files - synonym for -b.",
" -l	Count lines in the input files.",
" -u	Unix-style output; omits the descriptions of the counts (e.g., the",
"	\"bytes\" in \"5 bytes\").",
" -w	Count words in the input files.",

"If no options are given, wc provides all three counts.",
"",
"If no file names are given, wc reads from standard input.  All output is",
"written to standard output.",
"",
"A word is defined as \"optional spaces or tabs\" followed by text up to",
"the first space, tab, or end of line.",
"",
0 };

#include <stdio.h>
#ifdef vms
#include		<ssdef.h>
#include		<stsdef.h>
#define	IO_SUCCESS	(SS$_NORMAL | STS$M_INHIB_MSG)
#define	IO_ERROR	SS$_ABORT
#endif
/*
 * Note: IO_SUCCESS and IO_ERROR are defined in the Decus C stdio.h file
 */
#ifndef	IO_SUCCESS
#define	IO_SUCCESS	0
#endif
#ifndef	IO_ERROR
#define	IO_ERROR	1
#endif

int	dobytes = 0;		/* Show byte count	*/
int	dowords = 0;		/* Show word count	*/
int	dolines = 0;		/* Show line count	*/
int	ustyle	= 0;		/* Unix-style display	*/
long	twords	= 0;
long	tlines	= 0;
long	tbytes	= 0;
char	file_name[256];

main(argc, argv)
char *argv[];
{
	register int	i, nfiles;
	register FILE	*fp;
	int		nopts,gotcha;

#ifdef vms
	argc = getredirection(argc,argv);
#endif

	if (argc > 1 && argv[1][0] == '?') {
		help();
		exit(IO_ERROR);
	}

	for (nopts = 0, i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {

			case 'b':
			case 'c':
				dobytes++;
				break;
			case 'l':
				dolines++;
				break;
			case 'u':
				ustyle++;
				break;
			case 'w':
				dowords++;
				break;
			default:
				usage();
				exit(IO_ERROR);
			}
			argv[i] = NULL;
			nopts++;
		}
	}

	if (dobytes == 0 && dowords == 0 && dolines == 0)
	{	dobytes++;
		dowords++;
		dolines++;
	}
	nfiles = 0;
	if ((argc-nopts) < 2) {
	    ++nfiles;
	    count(stdin, NULL);
	}
	else {
#ifdef unix
	    for (i = 1; i < argc; ++i) {
		if (argv[i] == NULL)
			continue;
		if ((fp = fopen(argv[i], "r")) == NULL) {
		    perror(argv[i]);
		}
		else {
		    ++nfiles;
		    count(fp, argv[i]);
		    fclose(fp);
		}
	    }
#else
	    for (i = 1; i < argc; ++i) {
		if (argv[i] == NULL)
			continue;
		if ((fp = fwild(argv[i], "r")) == NULL) {
		    perror(argv[i]);
		}
		else {
		    for (gotcha = 0; fnext(fp) != NULL; gotcha++) {
			++nfiles;
			fgetname(fp, file_name);
			count(fp, file_name);
		    }
		    if (gotcha == 0)
			fprintf(stderr, "\"%s\": no matching files\n", argv[i]);
		}
	    }
#endif
	}
	if (nfiles > 1)
		output(tlines, twords, tbytes, "total");
	exit(IO_SUCCESS);
}

count(fp, filename)
FILE		*fp;			/* File pointer			*/
char		*filename;		/* File name string		*/
{
	register int c, inword;
	long lines;
	long words;
	long bytes;

	lines = 0;
	words = 0;
	bytes = 0;
	inword = 0;
	while((c = getc(fp)) != EOF) {
	    ++bytes;
	    if (c == ' ' || c == '\t' || c == '\n') {
		inword = 0;
		if (c == '\n')
		    ++lines;
	    }
	    else if (!inword) {
		++inword;
		++words;
	    }
	}
	twords += words;
	tlines += lines;
	tbytes += bytes;
	output(lines, words, bytes, filename);
}

output(lines, words, bytes, filename)
long		lines;
long		words;
long		bytes;
char		*filename;
{
	if (dolines)
		plural(lines, "line");
	if (dowords)
		plural(words, "word");
	if (dobytes)
		plural(bytes, "byte");
	if (filename != NULL)
	    printf(" %s", filename);
	printf("\n");
}

plural(value, what)
long		value;
char		*what;
{
	if (ustyle)
		printf("%8ld", value);
	else
		printf("%8ld %s%c", value, what, (value == 1) ? ' ' : 's');
}

usage()
/*
 * Give simple-minded help
 */
{
	fprintf(stderr,
 "Usage:  wc [-bcluw] [filename ...]\n"
		);
}

help()
/*
 * Give good help
 */
{
	register char	**dp;

	for (dp = documentation; *dp; dp++)
		printf("%s\n", *dp);
}
----------------getred.c----------------
/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 *
 * Modified, 24-Jan-86 by Jerry Leichter
 *	When creating a new output file, force the maximum record size to
 *	512; otherwise, it ends up as 0 (though the C I/O system won't write
 *	a record longer than 512 bytes anyway) which will cause problems if
 *	the file is later opened for APPEND - if the maximum record size is
 *	0, C will use the length of the longest record written to the file
 *	for its buffer!
 */

#ifdef	vms
#include	<stdio.h>
#include	<errno.h>

int
getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, it is erased
 * from the vector.  getredirection() returns a new argc value.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		argc = getredirection(argc, argv);
 *	}
 */
{
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			j;	/* Output index		*/
	int			file;	/* File_descriptor 	*/

	for (j = i = 1; i < argc; i++) {   /* Do all arguments	*/
	    switch (*(ap = argv[i])) {
	    case '<':			/* <file		*/
		if (freopen(++ap, "r", stdin,"shr=put") == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(errno);	/* Is a fatal error	*/
		}
		break;

	    case '>':			/* >file or >>file	*/
		if (*++ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if
		     * the argument was given as ">filename".
		     * access(name, 2) is TRUE if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) != NULL)
			    break;	/* Exit case statement	*/
			perror(ap);	/* Error, can't append	*/
			exit(errno);	/* After access test	*/
		    }			/* If file accessable	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  create(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var", "mrs=512"))
			== -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(errno);	/* is a fatal error	*/
		}			/* If '>' creation	*/
		break;			/* Exit case test	*/

	    default:
		argv[j++] = ap;		/* Not a redirector	*/
		break;			/* Exit case test	*/
	    }
	}				/* For all arguments	*/
	argv[j] = NULL;			/* Terminate argv[]	*/
	return (j);			/* Return new argc	*/
}
#else
getredirection(argc, argv)
int		argc;
char		*argv[];
/*
 * Dummy routine.
 */
{
	return (argv[0], argc);
}
#endif
----------------fwild.c----------------
/*
 * Fwild and fnext for vms.  See rmsio.c for more information.
 */

#include	<stdio.h>
#include	<rms.h>
#include	<ssdef.h>
#include	<descrip.h>
#include	<ctype.h>

#define	TRUE		1
#define	FALSE		0
#define	EOS		0

typedef struct rmsstuff {
	int		flag;	/* Flag for nonwildcard calls	*/
	char		*wildmode;
	struct	FAB	fab;
	struct	NAM	nam;
	char		starname[NAM$C_MAXRSS + 1];
	char		filename[NAM$C_MAXRSS + 1];
} RMSSTUFF;

static RMSSTUFF *wilddata[_NFILE];	/* RMS struct for each file	*/

/*
 * rms->flag can take on the following values:
 *	ISWILD		A file with wild-card bytes.
 *	UNWILD		A file without wild-cards, unopened.
 *	UNWILD_OPENED	A file without wild-cards, opened.
 */
#define	ISWILD		0
#define	UNWILD		1
#define	UNWILD_OPENED	2

extern FILE	*cleanup();

FILE *
fwild(filename, mode)
char		*filename;
char		*mode;
/*
 * Do wildcard setup
 */
{
	register FILE		*fd;
	register RMSSTUFF	*r;
	register int		index;

	/*
	 * First get a file to work with.  In the process, make sure the mode
	 * we got passed is valid.  Note that every file we will open from
	 * here on already exists, so an open for write will fail (since the
	 * open will include the fully-expanded file name, including the
	 * version number).  Unfortunately, opening a file on the null device
	 * always succeeds.  So, to simplify matters, we just reject a "w"
	 * mode request right here.  (We could as well let it go and then find
	 * "no matching files" when all later open requests fail.  But this
	 * makes more sense.)
	 */	
	if (*mode == 'w' || (fd = fopen("_nl:", mode)) == NULL) {
		return (NULL);
	}
	/*
	 * Warning: the package depends on fileno(fd) remaining unchanged
	 * after calls to freopen().
	 */
	index = fileno(fd);
	/*
	 * If we've been here before, make sure buffers are released.
	 */
	cleanup(index);
	if ((r = malloc(sizeof (RMSSTUFF))) == NULL
	 || (r->wildmode = malloc(strlen(mode) + 1)) == NULL)
	    return(cleanup(index));
	strcpy(r->wildmode, mode);
	wilddata[index] = r;
	/*
	 * Setup the fab and nam blocks.
	 */
	r->fab = cc$rms_fab;			/* Initialize fab	*/
	r->nam = cc$rms_nam;			/*   and nam blocks	*/
	r->fab.fab$l_nam = &r->nam;		/* fab -> nam		*/
	r->fab.fab$l_fna = filename;		/* Argument filename	*/
	r->fab.fab$b_fns = strlen(filename);	/* filename size	*/
	r->nam.nam$l_esa = r->starname;		/* Expanded file name	*/
	r->nam.nam$b_ess = NAM$C_MAXRSS;	/* ... size		*/
	r->nam.nam$l_rsa = r->filename;		/* Result filename	*/
	r->nam.nam$b_rss = NAM$C_MAXRSS;	/* ... size		*/
	/*
	 * Parse the file name
	 */
	if (sys$parse(&r->fab) != RMS$_NORMAL)
	    return (cleanup(index));
	/*
	 * Success.  Null-terminate expanded file name and set flag to
	 * distinguish between "wild" and "non-wild" filenames.
	 */
	((char *)r->nam.nam$l_esa)[r->nam.nam$b_esl] = EOS;
	r->flag = ((r->nam.nam$l_fnb & NAM$M_WILDCARD) == 0) ? UNWILD : ISWILD;
	return (fd);
}

FILE *
fnext(fd)
FILE		*fd;
/*
 * Open the next valid file. return fd if successful, NULL if finished.
 */
{
	register int		index;
	register RMSSTUFF	*r;
	register int		errorcode;
	FILE			*test;

	index = fileno(fd);
	if ((r = wilddata[index]) == NULL || r->flag == UNWILD_OPENED) {
	    /*
	     * It wasn't ours, or wasn't a wildcard and
	     * has already been processed.
	     */
	    fclose(fd);
	    fd = NULL;
	}
	else if (r->flag == UNWILD) {
	    /*
	     * Not a wildcard file, first time through
	     */
	    fd = freopen(r->starname, r->wildmode, fd);
	    r->flag = UNWILD_OPENED;
	}
	else {
	    /*
	     * Look for the next match -- who says you can't write
	     * obscure structured code?
	     */
	    for (;;) {
		/*
		 * Look 'em up but skip any with
		 * protection violation errors.
		 */
		r->fab.fab$w_ifi = 0;		/* Internal file index	*/
		if ((errorcode = sys$search(&r->fab)) == RMS$_NORMAL) {
		    /*
		     * We have a file.  Open it if we have access rights.
		     * Determining this isn't as simple as it might be because
		     * the access() function checks the wrong thing:  It
		     * ignores ACL's and any special privileges.  So we just
		     * open the file twice.  (We have to check first since if
		     * we fail, freopen will free up our file pointer and we
		     * can't be sure of getting it back.)  Opening the file
		     * twice has one unfortunate side effect - it bumps the
		     * file revision by 2.  That's life.
		     */
		    ((char *)r->nam.nam$l_rsa)[r->nam.nam$b_rsl] = EOS;
		    if ((test = fopen(r->filename, r->wildmode)) == NULL) {
			/*
			 * Couldn't open it; try for another.
			 */
			continue;
		    }
		    else {
			/*
			 * Close up the test channel first (to avoid problems
			 * with sharing; then open the file.  We should
			 * normally be able to open the file.  It this
			 * fails, something is wrong inside the C library.
			 * (More likely, a timing problem occured - between
			 * our test and our actual open, someone deleted the
			 * file, or between our close and re-open, someone
			 * else opened it in an incompatible mode.)  In any
			 * case, we've lost our file descriptor, and there is
			 * no good way to recover; so we just return NULL and
			 * let the caller think that's all there is.
			 */
			fclose(test);
			fd = freopen(r->filename, r->wildmode, fd);
			break;
		    }
		}
		else if (errorcode == RMS$_PRV) {
		    /*
		     * sys$search() found something, but we don't have
		     * privileges to open it.  Look for another.
		     */
		    continue;
		}
		else {
		    /*
		     * Can't find a file.  This should be RMS$_NMF.
		     */
		    fclose(fd);
		    fd = NULL;
		    break;
		}
	    }
	}
	/*
	 * Cleanup if any errors
	 */
	if (fd == NULL) {
	    cleanup(index);
	}
	return (fd);
}

static FILE *
cleanup(index)
register int	index;
/*
 * Empty out any stored information
 */
{
	register RMSSTUFF	*r;

	r = wilddata[index];
	if (r != NULL) {
	    if (r->wildmode != NULL) {
		free(r->wildmode);
	    }
	    free(r);
	    wilddata[index] = NULL;
	}
	return (NULL);
}

