[net.sources] MANALIAS: create alias 'pointers' to man pages.

ksbszabo@watvlsi.UUCP (Kevin Szabo) (07/20/85)

I briefly announced this program in net.sources .  We have been
using the following code for a couple of months now and I find
it makes MAN much more useful.  Read the enclosed MAN page
and find out what the program is all about.  Please USE!, your
users will like it.

You may find that the test directory won't unpack properly on your
system. It uses the 4.2 directory flexnames, hence some filenames
will be too long.  It isn't vital to the distribution.
#
#----------------------
# Feed to SH in an empty directory.
echo x - Makefile
sed 's/^X//' >Makefile <<'!E!O!F!'
XBIN	=	/usr/lib
XMAN	=	/usr/man/man8
XCC	=	cc -O
X
Xmanalias:	manalias.c
X	$(CC) manalias.c -o manalias
X
Xinstall: manalias
X	cp manalias $(BIN)
X	strip $(BIN)/manalias
X
Xinstallman:
X	cp manalias.8 $(MAN)
X
Xclean:
X	-rm manalias
!E!O!F!
echo x - manalias.8
sed 's/^X//' >manalias.8 <<'!E!O!F!'
X.TH MANALIAS 8 "30 January 1985"
X.SH NAME
Xmanalias \- automatically create aliases for man pages
X.SH SYNOPSIS
X.B manalias 
X\-nqv <files>
X.SH OPTIONS
X.IP \-v
XVerbose. print out actions on stdout. Quite longwinded.
X.IP \-q
XDon't quit. Manalias has a threshold of MAX_WARNING messages
Xafter which it exits. If -q is specified it continues past the
Xthreshold. MAX_WARNINGS is presently 100.
X.IP \-p
XNo printing. Doesn't babble when it creates an alias
Xor finds one which doesn't agree with what it thinks it should be.
XThis is overriden by the verbose flag.
X.IP \-n
XNo action. Prints what it would do instead of doing it.
X.SH DESCRIPTION
X.B Manalias
Xscans unformatted manual pages and creates cross-links
Xto all the program or topic names mentioned in the title line of
Xthe manual page.  These cross-links enable a manual entry to be
Xfound using any of the names in the title line.
X.B Man(1)
Xwill follow ".so manX/manpage" that are found in files,
X.B manalias
Xwill create files with the ".so" contents by processing the original
Xman page.
X.B Manalias
Xexamines the lines between the first and second 
X.B .SH
Xof each of the file arguments and uses them to create the man aliases. 
XFor instance, the man page
Xbelow will have two aliases created.
X.nf
X
X::::::::::::::
Xman3/sin.3m
X::::::::::::::
X\&.TH SIN 3M
X\&.SH NAME
X\&sin, cos, tan \e\- trigonometric functions
X\&.SH SYNOPSIS
X\&.
X\&.
X\&.
X
XThe aliases created are -
X
X.ne 10v
X::::::::::::::
Xman3/cos.3m
X::::::::::::::
X\&.so man3/sin.3m
X
X::::::::::::::
Xman3/tan.3m
X::::::::::::::
X\&.so man3/sin.3m
X
X.fi
X.B Manalias
Xrecognizes a comment as something like 
X.I " \e\- comment "
Xor even \fI - comment\fR, and strips them out.
Xand strips them out.
X.SH MOTIVATION
XIt is bloody irritating to ask MAN for a man page,
Xhave MAN tell you it isn't there,
Xinvoke MAN -k,
Xand then have to ask MAN for the real man page.
XHopefully this will all go away now.
X.SH EXAMPLE
XThe program is usually invoked in a form similar to 
X\fBgetNAME\fR, i.e. you'll need a shell script such as:
X.RS
X.sp
X.nf
Xcd /usr/man
Xforeach i ( man* )
X	cd $i
X	manalias * |& mail $MAN_MAINTAINER
X	cd ..
Xend
X.fi
X.RE
X.SH AUTHOR
XKevin Szabo
X.SH FILES
X/usr/lib/manalias	\- executable
X.br
X/usr/src/???	\- source
X.SH DIAGNOSTICS
XErrors and warnings about bad options and unopenable
Xfiles go to stderr. Some output goes to
Xstdout. These are short reports of files that have the same name as an alias
Xfile should have, but have the incorrect contents. The first line of
Xthe file is printed, along with what 
X.B manalias
Xthinks it should be.
X.B Manalias
Xwill also tell you when it creates an alias, and what that alias is.
XBoth can be overridden with the \-p flag.
XIf a file is encountered that has the correct contents 
X.B manalias
Xis quiet unless verbose is specified, in which case it outputs a message.
XWhen verbose and/or no action are specified they send output to stdout.
X.SH BUGS
XFiles must be in the working directory,
Xi.e. pathnames containing '/' are not allowed.
X.sp
XManalias is quite picky about the form of a 
X.I ".so pathname"
Xsince it does a straight string compare against what it
Xwanted to put into the file. Since manalias is the only
Xprogram that should have generated the file this is 
Xnot worth fixing.
X.sp
X.B Man(1)
Xdoesn't like the alias format 
X.I ".so /absolute/path/name"
Xso 
X.B manalias
Xgenerates pathnames relative to /usr/man.
X.sp
XPeople don't expect a program like 
X.B manalias
Xto have a look at the first lines of their man page, hence
X.B manalias
Xapplies a few heuristics to create a list of keywords that are
Xused as aliases. Keywords are anything up to the first \e or \-.
XEverything after that is ignored. Hence imbedded troff commands
Xare ignored, plus all the keywords after them. White space (blanks
Xand tabs) and commas may separate keywords. Only the tail end
Xof a pathname is significant when used as a keyword. Keywords
Xare always lowercased and used that way.
!E!O!F!
echo x - manalias.c
sed 's/^X//' >manalias.c <<'!E!O!F!'
X/*
X *	Make Aliases 'links' for man pages. Given a list of man pages
X *	this program will examine them for a list of names following the
X *	first .SH macro. For each of these names `manalias' will create
X *	a file of with the contents '.so $cwd/filename'. MANALIAS
X *	will check that this file doesn't exist before it attempts to
X *	create it.
X *
X *				Author:	Kevin Szabo, VLSI Group
X *					University of Waterloo
X */
X
X#include	<stdio.h>
X#include	<ctype.h>
X#include	<sys/param.h>
X
X#if !( defined( SYSIII ) || defined( SYSV ) )
X#define	strchr	index
X#define	strrchr	rindex
X#endif
X
Xextern	char	*getwd();	/* Library function definitions		*/
Xextern	char	*strrchr(),
X		*strchr(),
X		*strcat();
Xextern	char	*strncpy(),
X		*strcat(),
X		*strcpy();
X
X#define	FATAL		1
X#define	WARNING		0
X#define	MAX_WARNINGS	100	/* max number of warnings before exit */
X#define	MAXKEYLENGTH	300	/* stringlength of string that holds keys */
X
X#define	FOUND_COMMENT	1	/* results from massage_line */
X#define	NO_COMMENT	0
X
X#define	MAN_DIR	"/usr/man/"	/* sigh. I wish man was smarter */
X
X#define	USAGE	"usage: %s -v(erbose) -n(o action) -q(uit) -p(rint) files"
X
Xchar	*progname;		/* pointer to argv[0]	*/
Xchar	cwd[MAXPATHLEN];	/* current working directory */
X
Xint	verbose	= 0;		/* spew my guts out		*/
Xint	noaction = 0;		/* just print what was to be done */
Xint	noquit = 0;		/* don't quit when max-warning exceeded */
Xint	noprint = 0;		/* don't print what we did	*/
X
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	char		*s, *src, *dst;
X
X	progname = argv[0];
X	if( getwd( cwd ) == NULL )
X		error( FATAL, "can't get working dir: %s", cwd );
X	/*
X	 * Okay, kludge time (already you say? The function hasn't started!).
X	 * Man doesn't understand '.so /usr/man/manX/xxx', but it does
X	 * understand '.so manX/xxx'. Thus we look at our current working
X	 * directory, if it is prefixed with /usr/man we delete the prefix.
X	 * Yuck.
X	 */
X	if ( 0 == strncmp(cwd,MAN_DIR,strlen(MAN_DIR)) ) {
X		dst = cwd;
X		src = cwd + strlen( MAN_DIR );
X		while( *src ) {
X			*dst++ = *src++;
X		}
X		*dst = *src;			/* copy the null */
X	}
X
X	/* parse the option list */
X	while( --argc > 0 && (*++argv)[0] == '-' )
X		for( s=argv[0]+1; *s != '\0'; s++ )
X			switch (*s) {
X			case 'v':
X				verbose = 1;
X				break;
X			case 'p':
X				noprint = 1;
X				break;
X			case 'n':
X				noaction = 1;
X				break;
X			case 'q':
X				noquit = 1;
X				break;
X			default:
X				error( FATAL, "unknown option '%.1s'", s );
X			}
X	if ( argc <= 0 ) 
X		error( FATAL, USAGE, progname );
X	if ( verbose ) {
X		printf("%s: current directory is '%s'.\n",progname,cwd);
X	}
X	if ( verbose && noprint ) {
X		error(WARNING,"verbose option overrides noprint","");
X		noprint = 0;
X	}
X
X	/* create aliases for the files left */
X	while (argc > 0) {
X		make_aliases( *argv++ );
X		argc--;
X	}
X	exit( 0 );
X}
X
X
X
X/*
X * Given a filename, open it and get information.
X * attempt to create files with the contents '.so cwd/filename' as aliases
X * for man(1)
X */
X
Xmake_aliases( filename )
X	char *filename;
X{
X	char		headbuf[BUFSIZ];
X	char		linbuf[BUFSIZ];
X	static	char	keys[MAXKEYLENGTH];
X	int		keycharleft;
X	int		incomment;
X	char		*slash;
X
X	/* Find if filename refers to sub-directories..we can't handle them */
X	slash = strchr( filename, '/' );
X	if( slash != NULL ){
X		error( WARNING,
X		    "cannot process '%s', files must be in current directory",
X		    filename );
X		return;
X	}
X
X	if (freopen(filename, "r", stdin) == NULL) {
X		error( WARNING, "could not open '%s' for reading", filename);
X		return;
X	}
X	for (;;) {
X		if (fgets(headbuf, sizeof headbuf, stdin) == NULL){
X			error( WARNING, "file '%s' has no .TH line", filename );
X			return;
X		}
X		if (headbuf[0] != '.')
X			continue;
X		if (headbuf[1] == 'T' && headbuf[2] == 'H')
X			break;
X		if (headbuf[1] == 's' && headbuf[2] == 'o'){
X			/* skip files with just .so filename */
X			if ( verbose )
X				printf("%s:file '%s' .so's a file, skipped.\n",
X				    progname, filename );
X			return;
X		}
X	}
X	for (;;) {
X		if (fgets(linbuf, sizeof linbuf, stdin) == NULL){
X			error( WARNING, "file '%s' has no .SH line", filename );
X			return;
X		}
X		if (linbuf[0] != '.')
X			continue;
X		if (linbuf[1] == 'S' && linbuf[2] == 'H')
X			break;
X	}
X	/*
X	 * read lines following the `.SH NAME' construct and append
X	 * them to the `keys' string. Stop appending when another .SH
X	 * is found, or if a comment is found ( the \- string )
X	 */
X
X	*keys = '\0';				/* null string */
X	keycharleft = MAXKEYLENGTH;
X	for ( incomment=NO_COMMENT; incomment != FOUND_COMMENT;) {
X		if (fgets(linbuf, sizeof linbuf, stdin) == NULL) {
X			error( WARNING, "file '%s' has only one .SH line",
X				filename );
X			return;
X		}
X		if (linbuf[0] == '.') {
X			if (linbuf[1] == 'S' && linbuf[2] == 'H')
X				break;
X			else
X				continue;	/* skip troff directives */
X		}
X		incomment = massage_line( linbuf );
X		keycharleft -= 2 + strlen( linbuf );	/* null+blank=2 chars */
X		if( keycharleft > 0 ) {
X			strcat( keys, linbuf );
X			strcat( keys, " " );
X		} else {
X			error( WARNING,"file '%s':too many keywords; ignored",
X				filename );
X			fprintf( stderr,"%s: keys are '%s'.\n",
X				progname, keys );
X			return;
X		}
X	}
X	if ( verbose ) printf("%s: file '%s', keys '%s'\n",
X				progname, filename, keys );
X
X	process_keywords( filename, keys );
X}
X
X/*
X *	Here we do the work of splitting out the individual keywords
X *	from the keywords string.The extension that is present on the filename
X *	is tacked onto each key and we attempt to create an alias by
X *	that name.
X */
X
Xprocess_keywords( filename, keywords )
X	char	*filename, *keywords;
X{
X	char	*basename();
X
X	char	suffix[20];	/* holds the extension of filename (eg. .3m) */
X	char	cur_token[80];	/* the alias that we are currently processing */
X	char	*dot;		/* pointer to '.' in filename		*/
X
X	register char	
X		*token,		/* pointer to current keyword		*/
X		*nextoken;	/* pointer to next keyword		*/
X
X
X	dot = strrchr( filename, '.' );
X	if ( dot == NULL )
X		*suffix = '\0';
X	else {
X		if( strlen( dot ) >= sizeof( suffix ) ) {
X			error( WARNING,"suffix of '%s' is too long", filename );
X			return;
X		}
X		strncpy( suffix, dot, sizeof( suffix ) );
X	}
X	suffix[sizeof(suffix)-1] = '\0';	/* ensure string is terminated*/
X
X	nextoken = keywords;
X
X	while( *nextoken == ' ' ) *nextoken++='\0'; /*strip leading blanks */
X
X	while( *nextoken != NULL ) {
X		token = nextoken;
X		while( *nextoken != NULL && *nextoken != ' ' ) /* skip token */
X			nextoken++;
X
X		while( *nextoken == ' ' )	/* null terminate `token' */
X			*nextoken++ = '\0';
X
X		if( (strlen(token)+strlen(suffix)) >= sizeof(cur_token) ) {
X			error( WARNING,"file '%s': alias is too long",filename);
X			fprintf( stderr,"%s: %s%s\n", progname, token, suffix );
X			continue;
X		}
X		strcpy( cur_token, basename(token) );
X
X		/* malformed pathnames could give us garbage aliases */
X		if( strlen( cur_token ) == 0 ) {
X			if (verbose) printf("%s: alias '%s' skipped.\n",
X						progname,token);
X			continue;
X		}
X		strcat( cur_token, suffix );
X		make_one_alias( filename, cur_token );
X	}
X}
X
X/*
X *	The actual alias is created here. Files are checked for prior
X *	existance, and junk is printed out if they exist and don't
X *	contain what we think they should contain. Under no circumstances
X *	is an already existing file touched/mangled/spindled or mutilated.
X *	If the alias we are trying to create matches the filename
X *	we just return, since there is no alias required. Also, if
X *	through our general mangling we end up with a null file name
X *	we just return.
X */
X
Xmake_one_alias( filename, alias )
X	char	*filename, *alias;
X{
X	FILE	*aliasfile;
X	char	
X		aliasline[BUFSIZ];
X
X	if ( verbose ) printf("%s: file '%s' processing alias '%s'.\n",progname,
X							filename, alias );
X	if ( strcmp( filename, alias ) == 0 ) {
X		if (verbose) printf("%s: alias '%s' skipped.\n",progname,alias);
X		return;
X	}
X	sprintf( aliasline, ".so %s/%s\n" , cwd, filename );
X	aliasfile = fopen( alias, "r" );
X	reset_sys_error();
X	if( aliasfile != NULL ) {
X		check_alias( alias, aliasfile, aliasline, filename );
X		fclose( aliasfile );
X		reset_sys_error();
X		return;
X	}
X	if( noaction ) {
X		printf("%s: put in file '%s' < %s", progname, alias, aliasline);
X	} else {
X		aliasfile = fopen( alias, "w" );
X		if ( aliasfile ) {
X			fputs( aliasline, aliasfile );
X			fclose( aliasfile );
X			if (!noprint) printf("%s: alias '%s' made for '%s'.\n",
X					progname, alias, filename );
X		} else
X			error(WARNING,"could not open '%s' for writing", alias);
X	}
X	reset_sys_error();
X}
X
X/*
X *	Check the existing file's contents against the desired
X *	contents. If printing or verbose we chatter about what
X *	we found.
X */
X
Xcheck_alias( alias, aliasfile, aliasline, filename )
X	FILE	*aliasfile;
X	char	*alias, *aliasline, *filename;
X{
X	char	linebuf[BUFSIZ];
X	int	alias_ok;
X
X	if( fgets( linebuf, sizeof(linebuf), aliasfile) == NULL ) {
X		error( WARNING,"read of alias file '%s' failed", alias);
X		return;
X	} 
X	alias_ok = (strcmp(aliasline,linebuf) ==  0);
X
X	if ( !alias_ok && !noprint ) {
X		printf("%s: alias file '%s' exists for manpage '%s'.\n",
X			progname, alias, filename );
X		printf("\tfirst line=%s\tdesired contents=%s",
X			linebuf, aliasline );
X	}
X	if ( alias_ok && verbose ) {
X		printf( "%s: alias file '%s' has correct contents\n",
X			progname, alias );
X	}
X}
X
X/*
X *	Massage line:  will convert a string to lower case, convert comma's and 
X *	tabs to spaces, delete trailing newlines, and recognize the ' \- '
X *	beginning of comment in a list of names (and delete it).
X */
X
Xint
Xmassage_line( cp )
X	register char *cp;
X{
X	while ( *cp ) {
X		if( isupper(*cp) ) *cp = tolower(*cp);
X		if( *cp == ','  || *cp == '\t') *cp = ' ';
X		if( *cp == '\\' || *cp == '-' ) {
X			*cp = '\0';
X			return( FOUND_COMMENT );
X		}
X		cp++;
X	}
X	if (*--cp == '\n')
X		*cp = 0;
X	return( NO_COMMENT );
X}
X
X/*
X *	basename: return the last part of a full pathname, with
X *	all the '/dir/' stuff stripped.
X */
X	char	*
Xbasename( path )
X	char	*path;
X{
X	char	*name;
X
X	if ( name=strrchr( path, '/' ) )
X		return( name+1 );
X	else
X		return( path );
X}
X/*
X *	Generate an error message. The program name is tacked onto the
X *	beginning so that a non-interactive user will know where the
X *	message came from. The system error list is examined if the
X *	errno variable shows that a system call generated an error.
X *	Since this variable is not automatically reset we reset it
X *	after the message is printed.
X *					Based on routine in Kernighan & Pike
X */
Xerror( severity, format, string )
X	int	severity;
X	char	*format, *string;
X{
X	extern	int	errno, sys_nerr;
X	extern	char	*sys_errlist[], *progname;
X
X	static	warning_count = MAX_WARNINGS;
X
X	if ( progname )
X		fprintf( stderr, "%s: ", progname );
X	fprintf( stderr, format, string );
X	if ( errno > 0 && errno < sys_nerr )
X		fprintf( stderr, " (%s)", sys_errlist[errno] );
X	fprintf( stderr, ".\n" );
X
X	if ( severity == FATAL )
X		exit( 1 );
X	else if( --warning_count <= 0 && !noquit ) {
X		fprintf( stderr, "%s: Too many warnings, exiting.\n", progname);
X		exit( 1 );
X	} else
X		errno = 0;		/* reset system's error condition */
X}
X
X/*
X *	This short routine resets the system error variable so we don't
X *	falsely include system error messages when something calls error()
X *	later. This seems kind of kludgey to me, but I am unsure as how
X *	properly approach the matter.
X *
X *	Possibly I should have two error routines? One that examines the
X *	system error variable and one that doesn't? I don't know, and I
X *	don't care any more.
X */
Xreset_sys_error()
X{
X	extern	int	errno;
X
X	errno = 0;
X}
!E!O!F!
mkdir test
cd test
echo x - Makefile
sed 's/^X//' >Makefile <<'!E!O!F!'
Xusage:
X	@echo "type 'make <printactions>, or <alias> or <verbose> or <clean>'"
X
Xprintactions:
X	../manalias -n *
X
Xverbose:
X	../manalias -vn *
X
Xalias:
X	../manalias *
X
X#	don't change the *.* notation otherwise you will delete the
X#	Makefile!
Xclean:
X	-rm -f `grep -l '\.so' *.*`
!E!O!F!
echo x - badalias.1
sed 's/^X//' >badalias.1 <<'!E!O!F!'
XThis isn't the line that Manalias wants.
!E!O!F!
echo x - extraTroffDirectives.pqr
sed 's/^X//' >extraTroffDirectives.pqr <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH NAME
X.bp
Xextrajunk / \-
X.nh
X.SH SYNOPSIS
!E!O!F!
echo x - noSH.7
sed 's/^X//' >noSH.7 <<'!E!O!F!'
X
X.TH STRING 3  "19 January 1983"
X.UC 4
!E!O!F!
echo x - noTH.7
sed 's/^X//' >noTH.7 <<'!E!O!F!'
X..TH STRING 3  "19 January 1983"
X.UC 4
X.SH
Xjunk one two three
X.SH
!E!O!F!
echo x - oneSH.7
sed 's/^X//' >oneSH.7 <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH
Xwe only have one sh line
!E!O!F!
echo x - pagewithbadalias.1
sed 's/^X//' >pagewithbadalias.1 <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH NAME
Xbadalias \- string operations
X.SH SYNOPSIS
!E!O!F!
echo x - string.3
sed 's/^X//' >string.3 <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH NAME
Xstrcat, strncat, strcmp, strncmp, strcpy, strncpy, strlen, index, rindex \- string operations
X.SH SYNOPSIS
X.nf
X.B #include <strings.h>
X.PP
X.B char *strcat(s1, s2)
X.B char *s1, *s2;
X.PP
X.B char *strncat(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B strcmp(s1, s2)
X.B char *s1, *s2;
X.PP
X.B strncmp(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B char *strcpy(s1, s2)
X.B char *s1, *s2;
X.PP
X.B char *strncpy(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B strlen(s)
X.B char *s;
X.PP
X.B char *index(s, c)
X.B char *s, c;
X.PP
X.B char *rindex(s, c)
X.B char *s, c;
X.fi
X.SH DESCRIPTION
XThese functions operate on null-terminated strings.
XThey do not check for overflow of any receiving string.
X.PP
X.I Strcat
Xappends a copy of string
X.I s2
Xto the end of string
X.IR s1 .
X.I Strncat
Xcopies at most
X.I n
Xcharacters.  Both return a pointer to the null-terminated result.
X.PP
X.I Strcmp
Xcompares its arguments and returns an integer
Xgreater than, equal to, or less than 0, according as
X.I s1
Xis lexicographically greater than, equal to, or less than
X.IR s2 .
X.I Strncmp
Xmakes the same comparison but looks at at most
X.I n
Xcharacters.
X.PP
X.I Strcpy
Xcopies string
X.I s2
Xto
X.I s1,
Xstopping after the null character has been moved.
X.I Strncpy
Xcopies exactly
X.I n
Xcharacters, truncating or null-padding
X.I s2;
Xthe target may not be null-terminated if the length of
X.I s2
Xis
X.I n
Xor more.  Both return
X.IR s1 .
X.PP
X.I Strlen
Xreturns the number of non-null characters in
X.IR s .
X.PP
X.I Index
X.RI ( rindex )
Xreturns a pointer to the first (last) occurrence of character 
X.I c
Xin string
X.I s,
Xor zero if
X.I c
Xdoes not occur in  the string.
!E!O!F!
echo x - string.badextensionwhichistoolong
sed 's/^X//' >string.badextensionwhichistoolong <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH NAME
Xstrcat, strncat, strcmp, strncmp, strcpy, strncpy, strlen, index, rindex \- string operations
X.SH SYNOPSIS
X.nf
X.B #include <strings.h>
X.PP
X.B char *strcat(s1, s2)
X.B char *s1, *s2;
X.PP
X.B char *strncat(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B strcmp(s1, s2)
X.B char *s1, *s2;
X.PP
X.B strncmp(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B char *strcpy(s1, s2)
X.B char *s1, *s2;
X.PP
X.B char *strncpy(s1, s2, n)
X.B char *s1, *s2;
X.PP
X.B strlen(s)
X.B char *s;
X.PP
X.B char *index(s, c)
X.B char *s, c;
X.PP
X.B char *rindex(s, c)
X.B char *s, c;
X.fi
X.SH DESCRIPTION
XThese functions operate on null-terminated strings.
XThey do not check for overflow of any receiving string.
X.PP
X.I Strcat
Xappends a copy of string
X.I s2
Xto the end of string
X.IR s1 .
X.I Strncat
Xcopies at most
X.I n
Xcharacters.  Both return a pointer to the null-terminated result.
X.PP
X.I Strcmp
Xcompares its arguments and returns an integer
Xgreater than, equal to, or less than 0, according as
X.I s1
Xis lexicographically greater than, equal to, or less than
X.IR s2 .
X.I Strncmp
Xmakes the same comparison but looks at at most
X.I n
Xcharacters.
X.PP
X.I Strcpy
Xcopies string
X.I s2
Xto
X.I s1,
Xstopping after the null character has been moved.
X.I Strncpy
Xcopies exactly
X.I n
Xcharacters, truncating or null-padding
X.I s2;
Xthe target may not be null-terminated if the length of
X.I s2
Xis
X.I n
Xor more.  Both return
X.IR s1 .
X.PP
X.I Strlen
Xreturns the number of non-null characters in
X.IR s .
X.PP
X.I Index
X.RI ( rindex )
Xreturns a pointer to the first (last) occurrence of character 
X.I c
Xin string
X.I s,
Xor zero if
X.I c
Xdoes not occur in  the string.
!E!O!F!
echo x - toomanykeywords.4w
sed 's/^X//' >toomanykeywords.4w <<'!E!O!F!'
X.TH STRING 3  "19 January 1983"
X.UC 4
X.SH NAME
Xstrcat, strncat, strcmp, strncmp, strcpy, strncpy, strlen, index, rindex 
XThese functions operate on nullterminated strings.
XThey do not check for overflow of any receiving string.
X.PP
X.I Strcat
Xappends a copy of string
X.I s2
Xto the end of string
X.IR s1 .
X.I Strncat
Xcopies at most
X.I n
Xcharacters.  Both return a pointer to the nullterminated result.
X.PP
X.I Strcmp
Xcompares its arguments and returns an integer
Xgreater than, equal to, or less than 0, according as
X.I s1
Xis lexicographically greater than, equal to, or less than
X.IR s2 .
X.I Strncmp
Xmakes the same comparison but looks at at most
X.I n
Xcharacters.
X.PP
X.I Strcpy
Xcopies string
X.I s2
Xto
X.I s1,
Xstopping after the null character has been moved.
X.I Strncpy
Xcopies exactly
X.I n
Xcharacters, truncating or nullpadding
X.I s2;
Xthe target may not be nullterminated if the length of
X.I s2
Xis
X.I n
Xor more.  Both return
X.IR s1 .
X.PP
X.I Strlen
Xreturns the number of nonnull characters in
X.IR s .
X.PP
X.I Index
X.RI ( rindex )
Xreturns a pointer to the first (last) occurrence of character 
X.I c
Xin string
X.I s,
Xor zero if
X.I c
Xdoes not occur in  the string.
!E!O!F!
cd ..
cd ..
exit
-- 
Kevin Szabo' watmath!wateng!ksbszabo (U of W VLSI Group, Waterloo, Ont, Canada)