[net.sources] "apply" utility

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/20/85)

#!/bin/sh
# Self-unpacking archive format.  To unbundle, sh this file.
echo 'apply.1' 1>&2
cat >'apply.1' <<'END OF apply.1'
.TH APPLY 1V VMB
'\"	last edit:	85/10/20	D A Gwyn
'\"	SCCS ID:	@(#)apply.1	1.1
.SH NAME
apply \- apply a command to a set of arguments
.SH SYNOPSIS
.B apply
[
.BR \-a c
] [ \-n ] [
.B \-v
] [
.B \-w
] command args ...
.SH DESCRIPTION
.I Apply\^
runs the named
.I command\^
on successive batches of arguments
taken from
.I args\^
until they are all consumed.
The optional number
.I n\^
specifies the number of arguments per batch
(default 1);
this many arguments
(separated by spaces)
are appended to
.I command\^
each time it is run.
In the special case that
.I n\^
is zero,
.I command\^
is run without arguments
once for each
.IR arg\^ .
.P
However, if
.I command\^
contains any character sequence
consisting of the character
.I c\^
(default
.BR % )
followed by a digit
.I d\^
from 1 through 9,
.I n\^
is ignored and each such
.I cd\^
pair is replaced by the
.IR d\^ th
following unconsumed
.IR arg\^ .
In this case, the maximum such digit
.I d\^
in
.I command\^
specifies the number of
.I args\^
consumed per batch.
.P
The
.B \-v
(``verbose'') option
causes
.I apply\^
to print each constructed command
before it is run.
The
.B \-w
option causes
a warning to be printed for any command that
returns a non-zero exit status.
.SH CAVEATS
The constructed command
is executed by
.IR system\^ (3S),
so watch out for shell metacharacters in
.I command\^
or
.IR args\^ .
It is best to enclose complicated commands in single quotes \'\ \',
to prevent immediate interpretation by the current shell process.
.P
It is necessary to choose an alternate
substitution prefix character
.IR c\^ ,
if the default
.B %
character must appear literally
followed by a positive digit
in the constructed command.
.SH EXAMPLES
The following shell command
inefficiently emulates the behavior of `ls':
.RS
$ \|\fIapply \|echo \|*\fP
.RE
.P
The following shell command
compares the `a' files against the corresponding `b' files:
.RS
$ \|\fIapply \|\-2 \|cmp \|a1 \|b1 \|a2 \|b2 \|...\fP
.RE
.P
The following shell command
runs
.IR date\^ (1)
20 times; like
`for i in \`seq 20\`; do date; done':
.RS
$ \|\fIapply \|\-0 \|date \|\`seq \|20\`\fP
.RE
.P
The following shell command
exchanges the names of the `a' files with the corresponding `b' files:
.RS
$ \|\fIapply \|\'mv \|%1 \|t; mv \|%2 \|%1; mv \|t \|%2\' \|a1 \|b1 \|a2 \|b2 \|...\fP
.RE
.SH "SEE ALSO"
find(1), pick(1V), sh(1).
.SH DIAGNOSTICS
Error messages,
as well as requested
verbose or warning messages,
are printed on the standard error output
and are meant to be self-explanatory.
``Command too long''
indicates that the result of adding arguments to the
.I command\^
would exceed some system limit
on the total length of a command line.
.RI `` N\^
args left over''
indicates that the number of
arguments per batch
did not exactly divide
the number of supplied
.IR args\^ .
.SH "EXIT CODES"
If all applications of
.I command\^
return zero exit status,
then so will
.IR apply\^ .
An exit status of
1 is returned if any
.I command\^
execution errors occur;
2 is returned for usage errors.
.SH AUTHOR
Douglas A. Gwyn, BRL/VLD-VMB (inspired by 8th Edition UNIX)
END OF apply.1
echo 'apply.c' 1>&2
cat >'apply.c' <<'END OF apply.c'
/*
	apply -- apply a command to a set of arguments

	last edit:	85/10/20	D A Gwyn

Usage:
	$ apply[ -a<c>][ -<n>][ -v][ -w] <command> <args ...>

	Runs the named <command> repeatedly, <n> (default 1) successive
	arguments from <args ...> passed to <command> each time.

	Special case:  If <n> is 0, runs <command> as if <n> were 1, but
	does not pass any arguments to the <command>.

	Special kludge:  If <command> contains <c> (default %) followed
	by a digit from 1 through 9, <n> is ignored and the <c><digit>
	pairs are replaced by the corresponding remaining unused <args>,
	consuming as many <args> as the largest <digit> in <command>.

	Added by DAG:  The -v (verbose) option prints each constructed
	command before executing it.  If -w is specified, commands that
	return nonzero exit status will cause a warning message.

Credit:
	This is a public domain implementation of the 8th Edition UNIX
	command of the same name, working entirely from the manual spec.
*/
#ifndef	lint
static char	sccsid[] = "@(#)apply.c	1.1";
#endif

#include	<ctype.h>
#include	<stdio.h>
#include	<string.h>

extern void	exit();
extern int	atoi();

#define	MAX_CMD_LEN	BUFSIZ		/* allowed command size */

static int	verbose = 0;		/* set for command printout */
static int	warn = 0;		/* set for failure warnings */
static char	c = '%';		/* magic substitution prefix */
static int	n = 1;			/* real arguments per command */
static int	inc = 1;		/* args consumed per command */
static char	*command;		/* command template */
static char	buf[MAX_CMD_LEN+1];	/* shell command built here */
static int	errors = 0;		/* 1 if any execution errors */

#define	ToDigit( c )	((c) - '0')	/* convert char to int digit */

static void	LeftOver(), PutBuf(), Usage();


main( argc, argv )
	int	argc;
	char	*argv[];
	{
	/* process options */

	while ( --argc > 0 && (*++argv)[0] == '-' )
		if ( (*argv)[1] == 'v' && (*argv)[2] == '\0' )
			verbose = 1;		/* "-v" */
		else if ( (*argv)[1] == 'w' && (*argv)[2] == '\0' )
			warn = 1;		/* "-w" */
		else if ( (*argv)[1] == 'a' )	/* "-a<c>" */
			{
			/* wish we could use getopt() here */

			if ( (c = (*argv)[2]) != '\0'
			   && (*argv)[3] != '\0'
			  || (c == '\0'
			   && (--argc <= 0
			    || (c = (*++argv)[0]) == '\0'
			    || (*argv)[1] != '\0'
			      )
			     )
			   )	{
				(void)fprintf( stderr,
					       "apply: bad -a value\n"
					     );
				Usage();
				}
			}
		else if ( isdigit( (*argv)[1] ) )	/* "-<n>" */
			{
			if ( (n = atoi( &(*argv)[1] )) > 0 )
				inc = n;
			/* else inc = 1; */
			}
		else	{
			(void)fprintf( stderr,
				       "apply: unknown option \"%s\"\n",
				       *argv
				     );
			Usage();
			}

	/* save command template */

	if ( --argc < 0 )
		{
		(void)fprintf( stderr, "apply: missing command\n" );
		Usage();
		}

	command = *argv++;

	/* execute constructed command repeatedly */

	while ( argc > 0 )		/* unconsumed <args> remain */
		{
		register char	*cp;	/* -> <command> template */
		register char	*bp;	/* -> construction buffer */
		register int	max = 0;	/* maximum <digit> */

		/* copy <command> template into construction buffer,
		   looking for and performing <c><digit> substitution */

		for ( bp = buf, cp = command; *cp != '\0'; ++cp )
			if ( *cp == c	/* <c> matched, try <digit> */
			  && isdigit( cp[1] ) && cp[1] != '0'
			   )	{
				register char	*ap;	/* -> arg[.] */
				register int	digit;	/* arg # */

				++cp;	/* -> <digit> */

				if ( (digit = ToDigit( *cp )) > max )
					max = digit;

				if ( digit > argc )
					LeftOver( argc );

				for ( ap = argv[digit - 1];
				      *ap != '\0';
				      ++ap
				    )
					PutBuf( bp++, *ap );
				}
			else
				PutBuf( bp++, *cp );

		if ( max > 0 )		/* substitution performed */
			{
			argc -= max;
			argv += max;
			}
		else	{
			register char	*ap;	/* -> arg[.] */
			register int	i;	/* arg # - 1 */

			if ( inc > argc )
				LeftOver( argc );

			/* append <n> arguments separated by spaces */

			for ( i = 0; i < n; ++i )
				{
				PutBuf( bp++, ' ' );

				for ( ap = argv[i]; *ap != '\0'; ++ap )
					PutBuf( bp++, *ap );
				}

			argc -= inc;
			argv += inc;
			}

		PutBuf( bp, '\0' );	/* terminate string */

		/* execute constructed command */

		if ( verbose )
			(void)fprintf( stderr, "apply: %s\n", buf );

		if ( system( buf ) != 0 )
			{
			errors = 1;
		
			if ( warn )
				(void)fprintf( stderr,
					       "apply: \"%s\" failed\n",
					       buf
					     );
			}
		}

	return errors;
	}


static void
PutBuf( bp, ch )			/* store character in buf[] */
	register char	*bp;		/* -> buf[.] */
	int		ch;		/* character to be stored */
	{
	if ( bp > &buf[MAX_CMD_LEN] )
		{
		(void)fprintf( stderr, "apply: command too long\n" );
		exit( 1 );
		}

	*bp = ch;
	}


static void
LeftOver( argc )			/* warn about LeftOver args */
	int	argc;			/* how many left over */
	{
	(void)fprintf( stderr,
		       "apply: %d arg%s left over\n",
		       argc,
		       argc == 1 ? "" : "s"
		     );
	exit( 1 );
	}


static void
Usage()					/* print usage message */
	{
	(void)fprintf( stderr,
	"Usage:\tapply [-a<c>] [-<n>] [-v] [-w] <command> <args ...>\n"
		     );
	exit( 2 );
	}
END OF apply.c
echo 'apply.mk' 1>&2
cat >'apply.mk' <<'END OF apply.mk'
#	apply.mk -- makefile for "apply" utility

#	last edit:	85/10/20	D A Gwyn

#	SCCS ID:	@(#)apply.mk	1.1

PRODUCT = apply
MKFILE	= ${PRODUCT}.mk
CFILES	= ${PRODUCT}.c
OBJS	= ${PRODUCT}.o
LDFLAGS = -n
BINDIR	= /vld/bin
MANDIR	= /usr/5lib/man/local/man1
BINPERM	= 755
MANPERM	= 664
TESTDIR = .
INS	= cp

.DEFAULT:
	$(GET) $(GFLAGS) -p s.$@ > ${TESTDIR}/$@
	touch $@

all:		${PRODUCT} ${PRODUCT}.1

${PRODUCT}:	${OBJS}
	$(CC) -o ${TESTDIR}/$@ ${LDFLAGS} ${OBJS}
	size ${TESTDIR}/$@
	touch $@

print:		${PRODUCT}.1 ${MKFILE} ${CFILES}
	( nroff -Tlp -man ${TESTDIR}/${PRODUCT}.1 ; \
	  pr ${MKFILE} ${CFILES} ${TESTDIR}/${PRODUCT}.1 ) | lpr

lint:		${CFILES}
	lint ${CFILES} > ${PRODUCT}.lint

compare:	all
	cmp ${BINDIR}/${PRODUCT} ${TESTDIR}/${PRODUCT}
	cmp ${MANDIR}/${PRODUCT}.1 ${TESTDIR}/${PRODUCT}.1

install:	all
	${INS} ${TESTDIR}/${PRODUCT} ${BINDIR}
	chmod ${BINPERM} ${BINDIR}/${PRODUCT}
	${INS} ${TESTDIR}/${PRODUCT}.1 ${MANDIR}
	chmod ${MANPERM} ${MANDIR}/${PRODUCT}.1

clean:
	-if vax; then rm -f ${CFILES}; fi
	-rm -f ${OBJS} ${PRODUCT}.lint

clobber:	clean
	-if vax; then rm -f ${TESTDIR}/${PRODUCT}.1; fi
	-rm -f ${TESTDIR}/${PRODUCT}
END OF apply.mk