tchrist@convex.com (Tom Christiansen) (01/08/91)
Here is a deluxe man program and associated utilities written in perl. It does everything any other man program does that I've ever seen, plus quite a bit more. There is documentation in the form of man pages as well as the paper I presented at the USENIX Large Installation Systems Administrations conference. There will be six parts to the distribution. Cat them together and then unshar. Here is a list of features. Note that the whatis databases are stored in ndbm format for rapid access, so you'll need ndbm. Changes since the last posting include: * extra utils * grep feature * online problem report feature * dynamic determination of MANPATH * bug fixes Thanks to everyone who's sent me bug reports or fixes. --tom Features include but are not limited to: * almost always faster than standard man (try 'man me') * take much less diskspace for catpages * supports per-tree tmac macros * compressed man and cat files * user-definable man path via $MANPATH or -M optoin * $MANPATH autoconfigged based on $PATH if not set * user-definable section search order via -S or $MANSECT. Thus programmers can get stty(3) before stty(1). * $PAGER support * show all the places you would find a man page (-w option) and in what order. * display all available man page on a topic (-a option) * no limits on what subsections go where (if you want to add 7x, ok) * support for multi-char sections like man1m/*.1m or manavs/*.avs * man -K for regexp apropos * grep through all the man pages in $MANPATH * section and subsection indexing for long man pages * support for alternate architectures docs on same machine * ability to run man on a local file * ability to easily troff (or preview) a man page * recognizes embedded filter directives for tbl and eqn * does the right thing for man tree that don't have DBM whatis files * support for connecting online problem reports to right man page * there's an extended usage message (man -U) for further help and to show current defaults. Here are some features of this version of makewhatis: * it's faster. * tries hard to make pretty output, stripping troff directives. * doesn't blow up on more files in a man directory than the shell will glob. * accepts troff string macros for the dashes in the the NAME section. * prints a diagnostic for a malformed NAME section. * detects linked (hard, soft, or via .so) man pages * finds *all* references in the NAME section. * recognizes MH's man macros (and .Sh from lwall). * many other things that makewhatis used to do wrong Here are some supporting utilities that are included: * catman -- new version that groks compressed files * catwhatis -- display the whatis databases * straycats -- find cat pages with no man page ancestor * countman -- find how many man pages you can get at * cfman -- find bad SEE ALSO references in man pages -- Tom Christiansen tchrist@convex.com convex!tchrist Perl programming is an *empirical* science! --Larry Wall in <10226@jpl-devvax.JPL.NASA.GOV>
tchrist@convex.COM (Tom Christiansen) (01/08/91)
#! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # man # This archive created: Mon Jan 7 15:58:51 1991 export PATH; PATH=/bin:/usr/bin:$PATH if test ! -d 'man' then echo shar: "creating directory 'man'" mkdir 'man' fi echo shar: "entering directory 'man'" cd 'man' echo shar: "extracting 'makewhatis.8'" '(9442 characters)' if test -f 'makewhatis.8' then echo shar: "will not over-write existing file 'makewhatis.8'" else sed 's/^ X//' << \SHAR_EOF > 'makewhatis.8' X.TH MAKEWHATIS 8 X.CX \" probably only makes sense on Convex machines X.\" X.de SB \" small and bold X.if !"\\$1"" \\s-1\\fB\&\\$1\\s0\\fR\\$2 \\$3 \\$4 \\$5 X.. X.\" X.de T \" switch to typewriter font X.ft TA \" probably want CW if you don't have TA font X.. X.\" X.de TY \" put $1 in typewriter font X.if t .T X.if n ``\c X\\$1\c X.if t .ft P X.if n \&''\c X\\$2 X.. X.\" X.de M \" man page reference X\\fI\\$1\\fR\\|(\\$2\)\\$3 X.. X.SH NAME Xmakewhatis \- rebuild the whatis database X.SH SYNOPSIS X.B /usr/lib/makewhatis X[ X.B \-v X] [ X.B \-n X] [ X.B \-y X] [[ X.B \-M X] manpath ] X.SH DESCRIPTION XThe X.I makewhatis Xprogram Xrebuilds the text and X.M dbm 3X Xforms of the X.I whatis Xdatabase Xfrom the on-line manual pages for a given X\fImanpath\fP, or from X.I /usr/man Xif none is supplied. XThese files are Xused by the X.M man 1 , X.M whatis 1 , Xand X.M apropos 1 Xprograms to locate man pages and to print out the one-line Xdescriptions given by X.I whatis Xand X.I apropos\c X\&. XThe X.I makewhatis Xprogram Xshould be run whenever new man pages are added so these programs can Xfind them. X.PP XEach component of the colon-delimited X.I manpath Xis interpreted to be the root Xof a new tree containing directories of the form \fIman*\fP, Xwithin which are unformatted man pages of the form \fI*.*\fP. A separate X.I whatis Xfile and corresponding X.I dbm Xfiles, X.I whatis.pag Xand X\fIwhatis.dir\fP, Xwill be placed at the root of that man tree. XFiles or directories ending in tilde (\fB~\fP) or in \fB.bak\fP or \fB.old\fP (in either case) Xwill be skipped, as will a section named \fBman0\fP, should it exist. X.PP XIn general, files in section \fBmanX\fP should end in \fB.X*\fP, where X\fBX\fP is a section of the manual like \fB1\fP or \fB3x11\fP. This means Xit's ok to put things ending in \fB1c\fP in \fBman1\fP, but not things Xending in \fB3s\fP. Any man page whose name doesn't match these criteria Xwill generate a warning, but will be processed anyway. The exception Xto this is the \fBmano\fP subdirectory, which has Xtraditionally been the receptacle of all obsolete Xman pages irrespective of their file extension. A better way to do that Xwould be to have an entire man tree dedicated to this, as in X\fI/usr/old/man/\fP. X.PP XMultiple entries occurring in the X.SB NAME Xsection or as links (hard, soft, or via \fB.so\fP inclusion) will all be Xstored under the same man page name. This method can save significant amounts Xof disk space because it guarantees that only one cat page need be generated, Xregardless of how many ways you can get at the corresponding man page. XMaintaining aliases as links or via a one-line file that \fB.so\fP's the Xreal man page (once the only way to do this) is still supported although Xno longer Xrequired; mere inclusion in the X.SB NAME Xsection is sufficient. X.PP XEmbedded point and font changes will be removed from the output, and X\fItroff\fP string macros for hyphens and underscores will be Xtranslated into their corresponding X\s-1ASCII\s0 Xrepresentations. In Xgeneral, all that can be done will be done to produce nicely readable output for X.M whatis 1 . X.PP XThe X.B \-v Xflag will generate verbose output, reporting such things as each new man tree Xexamined, each subdirectory, each file that is opened, and each entry in the X.SB NAME Xsection that is found and stored. Simple tracing information is printed Xto the standard output, while to standard error are directed Xmore serious warnings. X.PP XThe X.B \-n Xoption can be used to check whether the database needs rebuilding. It Xwill report on the first file in each man path that is out of date. Note Xthat this is the only automated way to determine this; neither X.I makewhatis Xnor X.I man Xwill realize that the database is out of date, so you should be careful Xto rebuild it whenever new man pages are added. X.PP XThe X.B \-y Xoption is similar, but after finding something out of date, X.I makewhatis Xwill automatically rebuild its database. X.PP XSupport for compressed man pages is provided in the following way: Xif the name of the subdirectory itself ends in \fB.Z\fP, as in \fBman8.Z\fP, Xthen all its files are assumed to have been compressed with X.M compress 1L . XAlternatively, individual files ending in \fB.Z\fP are also Xconsidered to be compressed. XCompressed files are processed with X.M zcat 1L . X.SH EXAMPLES X.ft TA X.nf X.ta 3i X% makewhatis -y # build /usr/man database only if out of date X% makewhatis /usr/local/man # make local man page database X% makewhatis -v ~/man # make personal man page database verbosely X% makewhatis -n $MANPATH # tell if any dbase in $MANPATH out of date X.ft P X.fi X.SH FILES X.nf X.ta \w'/usr/man/whatis.pag 'u X\fI/usr/man/whatis\fP default whatis database, text version X\fI/usr/man/whatis.pag\fP \fIdbm\fP index file for default whatis database X\fI/usr/man/whatis.dir\fP \fIdbm\fP data file for default whatis database X\fI/usr/man/man*/*.*\fR default (unformatted) man pages X\fI/usr/man/cat*/*.*\fR formatted man pages X.SH "SEE ALSO" Xman(1), Xwhatis(1), Xapropos(1), Xperl(1), Xcompress(1L), Xdbm(3X), Xman(7), Xcatman(8) X.SH DIAGNOSTICS XNumerous diagnostics may be issued, especially if the X.B \-v Xflag has been given. Here are messages that may require Xfurther attention. The X.B %s Xand X.B %d Xfields Xin the descriptions indicate strings and integers respectively Xthat will be filled Xin appropriately at run time, while X.B %m Xindicates a standard system error message as Xdescribed in X.M intro 2 . XSee the source for further details. X.sp X.TY "Skipping non-man file: %s" X.in +5n XA file was found in a man directory that has no dot Xin its name. X.in -5n X.sp X.TY "%s has a funny extension to be in %s" X.in +5n XAn apparent man file was found whose extension doesn't Xmatch the name of the man file in which it was found. XThis restriction does not apply to X.B mano Xsubdirectories. X.in -5n X.sp X.TY "can't stat %s: %m" X.in +5n XThe X.M stat 2 Xcall returned an error. This can be caused by a Xsymbolic link pointing to a non-existent file. X.in -5n X.sp X.TY "can't open %s: %m" X.in +5n XThe X.M open 2 Xcall returned an error, which is listed. X.in -5n X.sp X.TY "can't chdir back to %s: %m" X.in +5n XAfter changing directory to one of the man subdirectories, X.I makewhatis Xcouldn't return to the initial root man tree. This is Xa fatal error. X.in -5n X.sp X.TY "makewhatis: %s: found %d entries in %d files" X.in +5n XFor the given root man directory, this many separate Xentry points were found for this many different files. X.in -5n X.sp X.TY "%s .so references non-existent %s" X.in +5n XA file contains a X.B .so Xreference to a missing man page. X.in -5n X.sp X.TY "%s's .TH thinks it's in %s" X.in +5n XThe man page's internal X.SB TH Xsection has a different idea of where it lives Xthan the actual file name. X.in -5n X.sp X.TY "trimmed troff string macro in NAME section of %s" X.in +5n XAn irresolvable X.I troff Xstring macro was found within the X.SB NAME Xsection of the man page. This may cause peculiar X.I whatis Xoutput. X.in -5n X.sp X.TY "%s: no separated dash in %s" X.in +5n XThe X.SB NAME Xsection contained no dash in it to separate the list Xof man entries from their descriptions. This will also Xcause odd X.I whatis Xoutput. X.in -5n X.sp X.TY "%s: truncating cmdlist from %d to %d bytes for DBM's sake" X.in +5n XThe X.SB NAME Xsection contained too many characters. Due to built-in Xlimitations of X.M dbm 3X Xdata entries, this entry was truncated. It will appear Xin X.I whatis Xoutput with a trailing X.TY \&... Xat the point of truncation. X.in -5n X.sp X.TY "%s: forgot my own name!" X.in +5n XA man page is stored in a file name that doesn't Xcorrespond to any of the entries in its X.SB NAME Xsection. X.in -5n X.sp X.TY "%s: no NAME lines, so has no whatis description!" X.in +5n XNo parsable entries in the X.SB NAME Xsection were discovered. X.in -5n X.sp X.TY "%s was a .so alias for %s, but %s's NAME section doesn't know it!" X.in +5n XA X.B .so Xreference was found to point to a man page whose own X.SB NAME Xsection didn't contain the name of the X.B .so Xfile. This may be due to old man page aliases that were Xnever de-installed when the base man page changed. X.in -5n X.sp X.TY "can't store %s: would break DBM" X.in +5n XThere were too many entries for a topic for it Xto be stored without exceeding inherent X.M dbm 3X Xdata size limitations. X.in -5n X.SH NOTES XThis version of X.I makewhatis Xis written entirely in the X.I perl Xprogramming language; it requires that X.I perl Xbe installed on the system to run. X.SH RESTRICTIONS XThe X.SB NAME Xsection should be a comma-separated list Xof aliases for this man page, followed by white space, Xa dash, more white space, and then the description. Pages Xnot conforming to this rule will still be found, but a diagnostic Xwill be printed and their descriptions will be left blank. X.PP XMan pages should be formatted using the X.M man 7 Xmacro set. The only exceptions to this are the man pages Xfrom the \fIRand MH Message Handling System\fP, Xwhose own internal macro set is also recognized, and Larry XWall's \fB.Sh\fP section header macro. X.PP X.I makewhatis Xtakes much longer to run if man pages are stored in compressed form. X.SH BUGS XNot all systems have X.I compress Xinstalled on them. X.SH AUTHOR XTom Christiansen X\fI<tchrist@convex.com>\fP, X\s-1CONVEX\s0 Computer Corporation X.SH COPYRIGHT XCopyright 1990 X\s-1CONVEX\s0 Computer Corporation. XYou are free to use, modify, and redistribute these scripts Xas you wish for non-commercial purposes provided that this Xnotice remains intact. SHAR_EOF if test 9442 -ne "`wc -c < 'makewhatis.8'`" then echo shar: "error transmitting 'makewhatis.8'" '(should have been 9442 characters)' fi chmod 664 'makewhatis.8' fi echo shar: "extracting 'man.1'" '(20992 characters)' if test -f 'man.1' then echo shar: "will not over-write existing file 'man.1'" else sed 's/^ X//' << \SHAR_EOF > 'man.1' X.TH MAN 1 X.CX \" probably only makes sense on Convex machines X.\" X.de SB \" small and bold X.if !"\\$1"" \\s-2\\fB\&\\$1\\s0\\fR\\$2 \\$3 \\$4 \\$5 X.. X.\" X.de T \" switch to typewriter font X.ft TA \" probably want CW if you don't have TA font X.. X.\" X.de TY \" put $1 in typewriter font X.if t .T X.if n ``\c X\\$1\c X.if t .ft P X.if n \&''\c X\\$2 X.. X.\" X.de M \" man page reference X\\fI\\$1\\fR\\|(\\$2\)\\$3 X.. X.SH NAME Xman, apropos, whatis \- display on-line reference manual information X.SH SYNOPSIS X.B man X[ X.B \-ltfikwuvthdgK X] X[ X.B \-M X.I manpath X] X[ X\fB-S\fIsections\fR X] X.if n .br X.if n .ti +4n X[ X\fB-T\fItroffproc\fR X] X.if t .br X.if t .ti +4n X[ X.I hwtype X] X[ X.I section X] X.if n .br X.if n .ti +4n X\fItopic\fB[/\fIindex\fR] X\&... X.sp X.B apropos X[ X.B \-uvdDK X] X[ X.B \-M X.I manpath X] X[ X.I hwtype X] X.I keyword X.sp X.B whatis X[ X.B \-uvdhD X] X[ X.B \-M X.I manpath X] X[ X.I hwtype X] X.I topic X\&... X.SH DESCRIPTION XThe X.I man Xprogram (and its links, X.I apropos Xand X.I whatis\c X) formats and displays information from the on-line Programmer's XReference Manual. It can display complete entries selected Xby topic or brief one-line summaries selected by keyword X(as X.I apropos Xor with the X.B \-k Xor X.B \-K Xflags) or by topic X(as X.I whatis Xor with Xthe X.B \-f Xflag). XWhen invoked in the simplest way, without any options and with a topic, Xit displays Xthe corresponding manual page formatted with X.M nroff 1 \&. XIf the Xstandard output is to a terminal, Xthe man page is piped by default through X.M more 1 , Xor else through Xthe program specified in the user's X.SB PAGER Xenvironment variable if set. X.PP XMan pages are stored on-line as X.I nroff Xsource files and are reformatted Xwhenever the unformatted Xversion is missing or has a newer modification date than the formatted version. XThe formatted version of the man page stored in Xthe corresponding X.B cat*/ Xdirectory if it exists Xand is writable by the current user. XThus X.I man1/stty.1 Xonce formatted Xis stored in X.I cat1/stty.1\c X\&. XOtherwise manual pages must be reformatted each time a user wishes Xto view them. XIf the root of the man tree from which the man page is taken contains Xthe file X.I tmac.an\c X, then this file is used in lieu of the Xstandard X.M man 7 Xmacros to reformat the page. Appropriate calls to X.M tbl 1 Xand either X.M neqn 1 Xor X.M eqn 1 Xare automatically Xinserted as needed. X.SS "Section Selection" XSome topics occur in more than one section of the manual. XTo select a man page from a particular section, put the section name Xin front of the topic, as in X.TY "man 4 tty"\c X\&. XFor backwards compatibility with other X.I man Xprograms, certain Xnon-numeric sections are recognized by longer aliases: X.B local Xmeans section X.B l\c X, X.B new Xmeans section X.B n\c X, X.B public Xmeans section X.B p\c X, and X.B old Xmeans section X.B o\c X\&. XIn other words, X.TY "man new csh" Xon the command line will search for a man page on the X.I csh Xin section X.I n Xof the manual. X.PP XThe applicable topics for each component in the current X.SB MANPATH Xare sorted according to their section. XThe default ordering is X.B ln16823457po\c X\&. Thus, X.M stty 1 Xwould be displayed before X.M stty 3 . XThis ordering can be Xoverridden on a site-wide basis by the system manager (see X.SB NOTES Xsection below), a per-user basis using the X.SB MANSECT Xenvironment variable, Xor a per-command basis using the \fB-S \fIsections\fR switch. Programmers Xmay prefer a default ordering beginning X.B 231 Xso that when they type X.TY "man wait" Xthey see the man page for the system call rather than the one for the shell command Xof the same name. XIf multi-character sections exist (\fIe.g.\fR some systems have a X.B man1m Xsection) Xor subsection ordering is desired, then colons must be used to separate Xthe sections. A \s-1FORTRAN\s0 programmer might prefer to set his X.SB MANSECT Xto X.B 3f:2:3:1:6:8\c X, so that when he asks for the Xmanual page for X.I system\c X, he gets X.M system 3F Xrather than the XC function of the same name. X.PP XCase is significant for Xthe topic itself, but not for the section. This allows Xyou to make a distinction between X.M cc 1 , Xthe C compiler, Xand X.M CC 1 , Xthe C++ translator. However, section X.B 3S Xis interpreted as section X.B 3s Xto accommodate users Xwho see a reference to the man page for X.M printf 3S , Xand don't realize that it's stored in the file X.B printf.3s\c X\&. X.SS "Search Strategy" XThe X.I man Xprogram by default consults the X.SB MANPATH Xenvironment variable for a list of complete man trees. XIf this variable is not set, then the user's X.SB PATH Xenvironment variable will be consulted instead. This way, if X.I /usr/local/bin Xis before X.I /usr/bin Xin the PATH variable, then X.I /usr/local/man Xwill be before X.I /usr/man Xin the dynamically determined X.SB MANPATH Xvariable. This is nice because it means the user need only add a X.SB PATH Xcomponent, and the X.SB MANPATH Xwill be automatically updated apppropriately. XThis search Xorder can be overridden on a site-wide basis by the system manager (see X.SB NOTES Xsection below) or on a per-user basis by setting the Xenvironment variable X.SB MANPATH . XThis is a colon-separated Xlist of directory names that contains subdirectories Xof the form X.B man*/\c X\&. A user keeping his own man pages under his home directory Xmight place a line like the following in his X.BI ~ /.cshrc Xfile: X.in +5n X.TY "setenv MANPATH $HOME/man:/usr/local/man:/usr/man" X.in -5n XThe X.SB MANPATH Xmay also be set on a per-command basis by using the X.B \-M Xcommand-line. X.PP XIt is possible to install several complete sets Xof man pages on one host. These alternate sets are Xassumed to be installed in X\fB\s-2MANALT\s0\fI/subdir\fR, where X.SB MANALT Xis an environment variable defaulting to X.I /usr/local/man Xand X.I subdir Xis assumed to be a complete man tree. XIf at least non-switch two arguments are supplied, Xthen the X.SB MANALT Xdirectory is consulted Xto see whether it contains a sub-directory whose names is the Xfirst argument. This makes it easy to say things like X.TY "man sun csh" Xto read the X.I csh Xman page from the installed set of X.I sun Xman pages. This is equivalent to saying X.TY "man -M /usr/local/bin/sun csh" " (assuming" X.SB MANALT Xis not set). X.SS "Indexing" XVery long man pages are often broken up into several major Xsubsections, whose tables of contents can be listed with the X.B \-i Xswitch. XTo access one of these subsections directly, Xspecify \fB/\fIindex\fR, Xwhere X.I /index Xis the name of the subsection. XFor example, Xto see the section on expressions in the X.M csh 1 Xman page, use X.TY "man csh/expr" Xto skip to that section first. XIf X.I index Xis not a valid section or Xsubsection header, or if none is given, Xa list of all valid section Xheaders is displayed and the user is prompted Xto select one. The special form of a missing subsection Xafter the slash means to go into continuous menu mode, returning to the Xsection menu after the manual page has been displayed. X.PP XThe match against the index entries is not case-sensitive, Xnor is it required to be at the beginning Xof the string. For example, this allows X.TY /expr Xto be Xused to look up the X.TY "Regular Expressions" Xsection. XHowever, matches that X.I do Xstart at the front of the Xheader are selected before those that do not. A Xnumeric section index as listed by the X.B \-i Xflag may also be given, with an index of X.B 0 Xmeaning the whole page. X.PP XMan page indices (tables of contents) are generated automatically when needed. XTo save time, Xif a writable X.B idx*/ Xsubdirectory exists in the root Xof the man tree, the index is left there under the Xsame name as its man page for quicker Xaccess in the future. Indices older than their Xparent man page are rebuilt upon demand. X.PP XAs a special case of indexing, the form X.I //string Xstarts the X.SB PAGER Xat an arbitrary X.I string Xin the text of the man page Xrather than at a subsection index. X.PP XNote that the X.I /index Xfeature may not be used in conjunction with the X.B \-l X(local file) switch. The X.B \-i Xswitch may still Xbe used with local files. X.SS "Online Problem Reports" XSome vendors provide their customers with electronically Xreadable problem reports; also, some sites may wish to Xmaintain their own list of known problems. XTo support inclusion of these in Xthe man pages, the X.I man Xprogram knows to look in a set of X.B pr* Xsubdirectories for additional source. For example, if you Xare reading the man page contained in X\fI/usr/man/man1/foo.1\fP, Xthen if there exists a file called X\fI/usr/man/pr1/foo.1\fP, this will be appended to the Xdata passed to X.I nroff Xor X.I troff Xas an additional X.SB "KNOWN PROBLEMS" Xsection. These files should be in X.I nroff Xformat. X.SH OPTIONS X.TP X.BI \-M manpath XChanges the search path for finding man pages for just Xthis command, overriding the current X.SB MANPATH Xenvironment variable or system default. XSee the section on X.SB "Search Strategy" Xfor further details. X.TP X.BI \-S sections Xsets the section and subsection ordering for just Xthis command, overriding the Xcurrent X.SB MANSECT Xenvironment variables or system default. XSee the X.B "Section Selection" Xfor further details. X.TP X.B \-f Xdirects X.I man Xto print out the applicable one-line descriptions Xfor each corresponding X.I topic\c X\&. XThese one-line descriptions are stored in separate X.I whatis Xfiles for each component in the Xspecified X.SB MANPATH X\&. If X.M dbm 3X Xversions of these Xdatabases exist, these are used for more rapid location of manual Xpages. See X.M makewhatis 8 Xfor directions on generating the X.I "whatis" Xdatabases. XThis switch is automatically enabled if X.I man Xis invoked as X.IR whatis . X.TP X.B \-k Xdirect X.I man Xto search the X.I whatis Xdatabases for any entry Xmatching the given X.I keyword\c X\&. XThis is useful when the precise topic is unknown; it reports Xthe topics that have to do with the given keywords, according Xto their X.I whatis Xdescriptions. XOutput will be sent to the user's X.SB PAGER . XThis switch is automatically enabled if X.I man Xis invoked as X.IR apropos . X.TP X.B \-K Xsame as X.B \-k Xexcept regular expressions as in X.M egrep 1 Xmay be used in the X.I keywords\c X\&. X.TP X.B \-g XGrep through all man pages for one or more X.I perl Xexpressions. Compressed man pages will be Xcorrectly processed Xby X.M zcat 1L . X.TP X.B \-t Xis used to typeset the manual pages, usually Xusing X.M troff 1L Xor a front-end for the same. XIf the Xdefault system typesetter is not desired, it can be overridden Xeither with the X.SB TROFF Xenvironment variable or the X.B \-T Xflag. X.TP X.BI \-T troffproc Xselects an alternate typesetting program, overriding Xthe current X.SB TROFF Xenvironment variable or system default. XIt is most Xoften used to select a previewer Xfor workstations that support previewing of X.I troff Xoutput. X.TP X.B \-l Xis used with specific file names rather than with Xtopics. The same processing as X.I man Xwould perform on a system Xmanual page will be performed on the given file. This is useful Xwhen first writing new manual pages or viewing ones not yet installed. XIt may be used in conjunction with other switches, such as X.B \-t\c X\&. The indexing feature is not available for local man pages except Xvia the X.B \-i Xswitch. X.TP X.B \-w Xis used to determine the full pathnames of Xthe files that X.I man Xwould display for the given topic. This is useful Xwhen the same topic occurs in more than one section of the manual or Xin more than one of the components in the supplied X.SB MANPATH . XThe files are listed in the order in which they would be presented Xto the user. X.TP X.B \-a Xdisplays all man pages for all possible matches for a given Xtopic. Its effect is similar to calling X.I man Xon all the files returned by the X.B \-w Xswitch. X.TP X.B \-i Xdisplays Xthe complete section and subsection index Xfor a given topic. X.TP X.B \-v Xprints out the current revision of the program Xas stored by X.M rcs 1 . X.TP X.B \-u Xdisplays a long usage message, piped through the user's X.SB PAGER . XIt includes the current defaults for X.SB PAGER , X.SB MANPATH , X.SB MANSECT , X.SB MANALT , Xand X.SB TROFF . X.TP X.B \-h Xforces X.I "man" Xto ignore the X.I whatis Xdatabases and search for its manual pages the slow way. This is the Xdefault behavior if no X.I whatis Xdatabase has been created for Xa particular man tree. X.TP X.B \-d Xtriggers debugging output. All externally invoked programs Xsuch as X.I nroff Xand X.I egrep Xwill have their arguments displayed Xbefore they are called. Certain other debugging information is also printed. X.TP X.B \-D Xcauses embedded backspaces and their preceding characters Xin cat pages to be stripped upon Xoutput. This is useful for producing utterly clean output, Xsuch as when being viewed in an X.M emacs 1 Xwindow. X.SH EXAMPLES X.T X.nf X.ta 20 X.\" .ne 5 Xman stty # show me the first stty man page Xman -w stty # which stty man pages are there? Xman 3 stty # show me the one from section 3 Xman -a tty # show all man pages on tty
tchrist@convex.COM (Tom Christiansen) (01/08/91)
X X.\" .ne 3 Xman old makewhatis # show me the makewhatis man page from mano Xman new csh # show me the csh man page from mann X X.\" .ne 3 Xman sun csh # show me the csh man page for suns X (assumes $MANALT/sun contains sun man pages) X X.\" .ne 6 Xman -i uucp # get list of sections in uucp man page Xman -ai tty # give list of sections on all tty pages Xman adb/bugs # check for BUGS section of adb man page Xman man/exam # start at this example section Xman ksh/ # select from section menu for ksh man page Xman sun cron/files # go to the FILES section of the sun cron man page X X.\" .ne 4 Xman -f rcs # what is rcs? Xwhatis rcs # same as previous Xwhatis vax rcs # same as previous, but only vax man pages X X.\" .ne 4 Xman -k rcs # what knows about rcs? Xapropos rcs # same as previous Xapropos sun rcs # same as previous, but only sun man pages X X.\" .ne 3 Xman -S231 wait # override system section ordering Xman -S3f:3s:3:2:1 system # subsection ordering X X.\" .ne 2 Xman -M\ \ ~/man hack # use ~/man as only man tree X X.\" .ne 4 Xman -t 4 tty # troff tty(4) man page Xman -at tty # troff all tty pages Xman -Tpnitroff perl # preview perl man page Xman -lTpnitroff ../foo.1 # preview local file as man page X X.\" .ne 3 Xman -l file.x # run man on local file Xman -tl file.x # print local file as man would Xman -il file.x # get section index on local page X.fi X.ft R X.\" .ne 5 X.SH ENVIRONMENT X.I Man Xexplicitly checks for the following environment variables; if they are Xpresent, they Xwill override its internal defaults: X.IP \fBMANPATH\fP 15 XThe colon-separated list of man trees for locating man pages. This Xmay itself be overridden by the command line flags X.B \-M Xor by Xspecifying an alternate hardware type. X.IP \fBMANSECT\fP 15 XThe default ordering for section and subsection sorting. It need only Xbe separated by colons if multi-character sections such as X.B man1m Xexist Xor if subsection ordering is desired, such as to place subsection X.B 3f Xin front of section X.B 3 . X.IP \fBMANALT\fP 15 XThe directory to check for alternate sets of man trees. This is useful Xfor storing the man pages for several different machine architectures or Xversions of the operating system. See the X.B Search Strategy Xsection Xfor details. X.IP \fBPAGER\fP 15 XThe user's preferred viewer of paged output. Note that often the pager Xitself will consult the environment; for example, X.I more Xlooks for a X.SB MORE Xvariable Xand X.I less Xlooks for a X.SB LESS Xvariable Xin the environment. X.IP \fBTROFF\fP 15 XThe preferred typesetter program. It should recognize X.I troff Xinput and switches. XThis may be overridden using the X.B \-T Xcommand line flag. X.SH FILES X.nf X.ta \w'/usr/lib/perl/getopts.pl 'u X\fI/usr/man\fR default man tree X\fI/usr/man/man*/*.*\fR unformatted (nroff source) man pages X\fI/usr/man/cat*/*.*\fR formatted man pages X\fI/usr/man/idx*/*.*\fR indices for section headers X\fI/usr/man/whatis\fR default whatis database, text version X\fI/usr/man/whatis.dir\fR \fIdbm\fP index file for default \fIwhatis\fP database X\fI/usr/man/whatis.pag\fR \fIdbm\fP data file for default \fIwhatis\fP database X\fI/usr/lib/perl/getopts.pl\fR required \fIperl\fR library file X\fI/usr/lib/perl/stat.pl\fR required \fIperl\fR library file X.fi X.SH "SEE ALSO" X.M egrep 1 , X.M perl 1 , X.M more 1 , X.M eqn 1 , X.M tbl 1 , X.M nroff 1 , X.M troff 1L , X.M compress 1L , X.M dbm 3X , X.M man 7 , X.M catman 8 , X.M makewhatis 8 X.SH NOTES XThis version of X.I man Xis written entirely in the X.M perl 1 Xprogramming language Xand thus requires that X.I perl Xbe properly installed on your system to run. XBecause X.I man Xis distributed in a script, it can be easily Xreconfigured by the system managers to suit their site's particular style. XThis is good, as some of the default settings are somewhat Xidiosyncratic to the set-up at the \s-1CONVEX\s0 home office. X.sp X.in +5n X\fBBe sure to save a copy of the Xoriginal \fIman\fP Xprogram before any modification.\fR X.in -5n X.ft R X.sp X.ne 4 XThings that can be configured include: X.in +5n X.IP \(bu 5 Xthe default X.SB PAGER Xand its flags (alternate flags can be provided for X.I less\c X) X.IP \(bu 5 Xsystem defaults for X.SB MANPATH , X.SB MANSECT , X.SB MANALT , Xand X.SB TROFF . X.IP \(bu 5 Xpaths for X\fItroff\fP, X\fInroff\fP, X\fItbl\fP, X\fIeqn\fP, X\fIneqn\fP, X\fIul\fP, X\fIcol\fP, X\fIegrep\fP, and X\fIcompress\fP X.IP \(bu 5 Xwhether you have compressed man pages and how they are stored X.IP \(bu 5 Xwhich section aliases you want (as in X.B public Xbeing recognized Xas section \fBp\fP) X.IP \(bu 5 Xwhether to recognize man pages whose X.SB NAME Xsections don't mention their Xown names X.in -5n X.PP XTo save disk space at the expense of execution time, a site may Xwish to run X.M compress 1L Xon the manual entries where available. The X.I man Xprogram Xunderstands how to read compressed man Xpages, and knows to create a compressed cat page if the source Xman page was compressed to start with. X.PP XWhen running on slow \s-1CPU\s0s, the start-up time for parsing the Xscript may be annoying. These sites can skip this parsing phase Xat each invocation of X.M man 1 Xby using \fIperl\fP's X.B \-u Xflag to dump a binary image of the interpreter. X.SH DIAGNOSTICS XSeveral self-explanatory diagnostics are possible, such as X.TY "No manual entry for xyzzy" , Xbut the following warnings may not be intuitive: X.sp X.TY "But what do you want from section %s?" X.in +5n XA section was specified but no topic. X.in -5n X.sp X.TY "No dbm file for %s: %m" X.in +5n X.br XThis means that the named man tree does not have an Xaccessible X.I dbm Xversion Xof its X.I whatis Xdatabase and that X.M makewhatis 8 Xshould be run on it. This only shows up when X.B \-d Xoption is used. \fB%m\fP is the appropriate X.M perror 3 Xmessage. X.in -5n X.sp X.TY "%s has disappeared -- rerun makewhatis" X.in +5n X.br XA man page has been removed since X.I makewhatis Xwas last run. XNote that new man pages being added will NOT be detected. X.in -5n X.sp X.TY "nroff of %s failed" X.br X.in +5n XFor some reason X.I nroff Xdid not exit correctly. The disk may Xbe mounted read-only, it might be full, or X.I nroff Xmay Xnot be installed on your system. Contact your system manager. X.in -5n X.sp X.TY "%s was length 0; disk full?" X.br X.in +5n XA cat page was empty. XThe X.I man Xprogram Xunlinks the useless cat page, Xissues this warning, and exits non-zero. Rerun the command and Xif the problem persists, contact your system manager. X.in -5n X.sp X.TY "/tmp not writable" X.br X.in +5n XThe X.I /tmp Xand X.I /usr/man/cat* Xdirectories are not writable by you. Contact your system manager. X.in -5n X.sp X.TY "No whatis databases found, please run makewhatis" X.br X.in +5n XThe X.I man Xprogram was unable to find a X.I whatis Xdatabase for use with X.I apropos Xor X.I man -k Xin any of the directories listed in the X.BR MANPATH . XHave your system manager run X.I makewhatis Xon the system manual page directories, and run X.I makewhatis Xon any personal manual page directories. X.in -5n X.sp X.TY "bad eval: %s" X.br X.TY "can't eval %s: %s" X.br X.in +5n XThese are internal errors that should never occur. Contact Xyour system manager, who should submit a problem report. X.in -5n X.SH RESTRICTIONS XOnce a X.M dbm 3X X.I whatis Xdatabase has been created for a particular man root, Xnew man pages will not be found unless the X.B \-h Xflag is Xused or until X.I makewhatis Xis rerun. X.PP XIf your X.SB PAGER Xis X.M more 1 , Xbold text shows up in the normal font; Xhowever, X.M less 1 Xdoes display bold text in your terminal's bold font. X.SH BUGS XThe manual is supposed to be reproducible either on the phototypesetter Xor on an \s-1ASCII\s0 terminal. XHowever, on a terminal, some information is necessarily lost. X.PP XThe X.B \-t Xflag for X.I man Xonly works if the host system supports X.IR troff . X.PP XNot all systems have X.I compress Xinstalled on them. X.SH AUTHOR XTom Christiansen X.I <tchrist@convex.com>\c X.SH COPYRIGHT XCopyright 1990 X\s-1CONVEX\s0 Computer Corporation. XYou are free to use, modify, and redistribute these scripts Xas you wish for non-commercial purposes provided that this Xnotice remains intact. SHAR_EOF if test 20992 -ne "`wc -c < 'man.1'`" then echo shar: "error transmitting 'man.1'" '(should have been 20992 characters)' fi chmod 644 'man.1' fi echo shar: "extracting 'catman'" '(4457 characters)' if test -f 'catman' then echo shar: "will not over-write existing file 'catman'" else sed 's/^ X//' << \SHAR_EOF > 'catman' X#!/usr/bin/perl X# X# perl rewrite of catman X# author: tom christiansen <tchrist@convex.com> X# X# Copyright 1990 Convex Computer Corporation. X# All rights reserved. X X$| = 1; X X$TBL = "tbl -D"; X$EQN = "eqn"; X$MAKEWHATIS = "/usr/lib/makewhatis"; X$COMPRESS = "compress"; X$NROFF = "nroff"; X$COL = "col"; X$CAT = "cat"; X$ZCAT = "zcat"; X X# Command to format man pages to be viewed on a tty or printed on a line printer X$CATSET = "$NROFF -h -man -"; X$CATSET .= " | $COL" if $COL; X Xumask 022; X Xdo 'getopts.pl' || die("can't do getopts.pl", $@?$@:$!, "\n"); Xdo 'stat.pl' || die("can't do stat.pl ", $@?$@:$!, "\n"); Xdo 'ctime.pl' || die("can't do ctime.pl ", $@?$@:$!, "\n"); X X X# -Z flag is planning for the future Xunless (&Getopts('dpnwZP:M:') && $ARGV <= 1) { X die "usage: $0 [-pnwZ] [-M manpath] [sections]\n"; X} X X$debug = $opt_d; X$makewhatis = !$opt_n; X$catman = !$opt_w; X$fakeout = $opt_p; X$compress = $opt_Z; X X($sections = shift) && X (@sections = split($sections =~ /:/ ? ':' : '', $sections)); X X($manpath = $opt_P) || X ($manpath = $opt_M) || X ($manpath = "/usr/man"); X Xpath: foreach $path (split(/:/,$manpath)) { X unless (chdir $path) { X warn "can't chdir to $path: $!"; X $status = 1; X next path; X } X &run ("$MAKEWHATIS $path") if $makewhatis; X next unless $catman; X print "chdir $path\n" if $debug; X Xmandir: foreach $mandir (<man*>) { X next if $sections && !grep($mandir =~ /man$_/, @sections); X print "considering $mandir\n" if $debug; X $found++; X ($catdir = $mandir) =~ s/man/cat/; X $catdir = "$path/$catdir"; X next unless -w $catdir; X opendir(mandir,$mandir); X Xmanpage: foreach $manpage ( readdir(mandir) ) { X local(@st_man, @st_cat); X next manpage if $manpage =~ /^\.{1,2}/; X X if ($manpage !~ /\S\.\S/) { X print "skipping non man file: $manpage\n" if $debug; X next manpage; X } X X next manpage if $manpage =~ /\.(old|bak|out)$/i; X next manpage if $manpage =~ /~$/; X X $zpage = $zdir || $manpage =~ /\.Z$/; X X $manpage = "$path/$mandir/$manpage"; X X ($catpage = $manpage) X =~ s,^(.*)/man([^\.]*)(\.Z)?/([^/]*)$,$1/cat$2$3/$4,; X X @st_man = &Stat($manpage); X @st_cat = &Stat($catpage); X X if ($st_cat[$ST_MTIME] < $st_man[$ST_MTIME]) { X $command = (($manpage =~ m:\.Z:) ? $ZCAT : $CAT) X . " < $manpage | $CATSET"; X X $command = &insert_filters($command, $manpage); X $command =~ s,-man,$path/tmac.an, if -e "$path/tmac.an"; X X $command .= "| $COMPRESS " if $catpage =~ /\.Z/; X X $command .= "> $catpage"; X X &reformat($command); X } X } X } X} X Xsub insert_filters { X local($filters,$eqn, $tbl, $_); X local(*PAGE); X local($command, $PAGE) = @_; X X X $PAGE = "$ZCAT < $PAGE|" if $PAGE =~ /\.Z/; X X (open PAGE) || die ("can't open $page to check filters: $!\n"); X X while (<PAGE>) { X if (/^\.EQ/) { X $_ = <PAGE>; X $eqn = 1 unless /\.(if|nr)/; # has eqn output not input X } X if (/^\.TS/) { X $_ = <PAGE>; X $tbl = 1 unless /\.(if|nr)/; # has tbl output not input X } X last if $eqn && $tbl; X } X close PAGE; X X $eqn && $_[0] =~ s/(\S+roff)/$EQN | $1/; X $tbl && $_[0] =~ s/(\S+roff)/$TBL | $1/; X X $_[0]; X} X X Xsub run { X local($command) = $_[0]; X X $command =~ s/^\s*cat\s*<?\s*([^\s|]+)\s*\|\s*([^|]+)/$2 < $1/; X $command =~ s/^([^|<]+)<([^Z|<]+)$/$1 $2/; X print STDERR "$command\n" if $debug || $fakeout; X if (!$fakeout && system $command) { X $status = 1; X printf STDERR "\"%s\" exited %d, sig %d\n", $command, X ($? >> 8), ($? & 255) if $debug; X } X return ($? == 0); X} X Xsub print { X local($_) = @_; X X if (!$inbold) { X print; X } else { X for (split(//)) { X print /[!-~]/ ? $_."\b".$_ : $_; X } X } X} X Xsub reformat { X local($_) = @_; X local($nroff, $col); X local($inbold) = 0; X X s/^\s*cat\s*<?\s*([^\s|]+)\s*\|\s*([^|]+)/$2 < $1/; X s/^([^|<]+)<([^Z|<]+)$/$1 $2/; X ($nroff, $col) = m!(.*)\|\s*($COL.*)!; X X print "$nroff | (this proc) | $col\n" if $debug; X X open (NROFF, "$nroff |"); X open (COL, "| $col"); X X select(COL); X X while (<NROFF>) { X s/\033\+/\001/; X s/\033\,/\002/; X if ( /^([^\001]*)\002/ || /^([^\002]*)\001/ ) { X &print($1); X $inbold = !$inbold; X $_ = $'; X redo; X } X &print($_); X } X X close NROFF; X if ($?) { X warn "$program: \"$nroff\" failed!\n"; X $status++; X } X close COL; X if ($?) { X warn "$program: \"$col\" failed!\n"; X $status++; X } X select(STDOUT); X} SHAR_EOF if test 4457 -ne "`wc -c < 'catman'`" then echo shar: "error transmitting 'catman'" '(should have been 4457 characters)' fi chmod 755 'catman' fi echo shar: "extracting 'WISHES'" '(538 characters)' if test -f 'WISHES' then echo shar: "will not over-write existing file 'WISHES'" else sed 's/^ X//' << \SHAR_EOF > 'WISHES' Xincremental makewhatis Xnew man(5) page Xnew catman(8) page Xadd to makewhatis(8) section on whatis format? Xcatman new switches X z compress X u uncompress X i initialize -- create cat* and idx* directories X r remove old cat pages first, warn of cat pages w/o man pages X R remove recursivley old cat dirs first, then invoke -i Xman - collapse case on lookups Xcfman - option to use dbm files Xman - use optimized perl wth eval trick for apropos unless GNU grep Xman - option to ask which page out of many; i.e. -a + selection menu SHAR_EOF if test 538 -ne "`wc -c < 'WISHES'`" then echo shar: "error transmitting 'WISHES'" '(should have been 538 characters)' fi chmod 664 'WISHES' fi echo shar: "extracting 'BUGS'" '(268 characters)' if test -f 'BUGS' then echo shar: "will not over-write existing file 'BUGS'" else sed 's/^ X//' << \SHAR_EOF > 'BUGS' Xcatman needs to reference the database instead Xof globbing to find manpage names to avoid duplicating Xoutput from .so's. X Xcfman doesn't use the database, so its idea of what Xman can find is wrong. X Xif man is invoked as whman, it doesn't get the right Xoptions string. SHAR_EOF if test 268 -ne "`wc -c < 'BUGS'`" then echo shar: "error transmitting 'BUGS'" '(should have been 268 characters)' fi chmod 664 'BUGS' fi echo shar: "extracting 'README'" '(636 characters)' if test -f 'README' then echo shar: "will not over-write existing file 'README'" else sed 's/^ X//' << \SHAR_EOF > 'README' Xread the man pages first. then check out the configuration section Xin man to make sure it's as you like it. X Xyou have to run makewhatis to build the database to make this work. Xyou should actually remove your catpages and rerun catman, Xwho will call /usr/lib/makewhatis, which it assumes to be mine. Xthe catpages may be in a slightly different format than you are used to. X Xyou will need ndbm. X Xyou might want to create idx* directories for speed. X Xless makes a better pager than more. X Xyou can read the paper at your leisure. it's not essential. X Xcopying is ok, just don't remove my name or try to sell it. Xread the man pages first. SHAR_EOF if test 636 -ne "`wc -c < 'README'`" then echo shar: "error transmitting 'README'" '(should have been 636 characters)' fi chmod 664 'README' fi echo shar: "extracting 'cfman'" '(10347 characters)' if test -f 'cfman' then echo shar: "will not over-write existing file 'cfman'" else sed 's/^ X//' << \SHAR_EOF > 'cfman' X#!/usr/bin/perl X# X# cfman v2.0: man page cross-referencer X# author: Tom Christiansen <tchrist@convex.com> X# date: 15 November 89 X# X# usage: cfman [ -d debug-devel ] [ -s sub-sections ] X# [ -p manpath ] [ -x xrefpath ] X X($iam = $0) =~ s%.*/%%; X X$] =~ /(\d+\.\d+).*\nPatch level: (\d+)/; Xdie "$iam: requires at least perl version 3.0, patchlevel 1 to run correctly\n" X if $1 < 3.0 || ($1 == 3.0 && $2 < 1); X X&Getopts('fd:s:p:P:x:') || &usage; X X$manpath = $opt_p if defined $opt_p; X$manpath = $opt_P if defined $opt_P; X$manpath = $ENV{'MANPATH'} unless $manpath; X$manpath = "/usr/man" unless $manpath; X@manpath = split(/:/,$manpath); X X$opt_x =~ /^:/ && ( $opt_x = $manpath . $opt_x ); X@xrefpath = $opt_x ? split(/:/,$opt_x) : @manpath; X X$debug = $opt_d; X$use_DBM = $opt_f; X X@sections = $opt_s ? split(/ */,$opt_s) : 1..8; X Xif ($debug) { X $" = ':'; X print "manpath is @manpath\n"; X print "xrefpath is @xrefpath\n"; X $" = ' '; X} X Xfile: foreach $file ( $#ARGV >= $[ ? @ARGV : '*.*' ) { X printf STDERR "considering %s\n", $file if $debug & 1; X $bingo = 0; Xtree: foreach $tree ( @manpath ) { X print "ROOT is $tree\n" if $debug; X if (!chdir $tree) { X warn "cannot chdir to $tree: $!"; X next tree; X } X $rootdir = $tree; X if ( $file =~ m#^/# ) { X &read_manpages($file); X next file; X } Xsection: foreach $section ( @sections ) { X &scan_section($tree,$section,$file); X } X } X print "no man pages matched \"$file\"\n" unless $bingo; X } X X Xexit 0; X X############################################################################ X# X# scan_section() X# X# checks a given man tree (like /usr/local/man) in a X# certain subsection (like '1'), checking for a certain X# file, like 'tty' (which mean 'tty.*', 'system.3*', or '*.*'. X# X# will recurse on a subsection name contaning a shell meta-character X# X############################################################################ X Xsub scan_section { X local ( $manroot, $subsec, $files ) = @_; X local ( $mandir ); X X $mandir = "man" . $subsec; X X X # subsec may have been ? or *; if so, recurse! X if ( &has_meta($mandir) ) { X for (<${mandir}>) { X if (&has_meta($_)) { X warn "bad glob of $mandir"; X last; X } X s/^man//; X &scan_section($manroot,$_,$files); X } X return; X } X X $files = "$files.*" unless $files =~ /\./; X X if (!chdir $mandir) { X warn "couldn't chdir to $mandir: $!\n" if $debug; X return; X } X X printf STDERR "chdir to %s of %s\n", $mandir, $manroot if $debug & 1; X X &read_manpages ( &has_meta($files) ? &glob($files) : ($files)); X X chdir('..'); X} X X############################################################################ X# X# read_manpages() X# X# passed a list of filename, which are man pages. opens each one X# verifying that the file really is in the place that the .TH line. X# skips to SEE ALSO section and then verifies existence of each X# referenced man page. X############################################################################ X X Xsub read_manpages { X local (@pages) = @_; X X local ($junk, $sopage, $basename, $line, $page, $pname, $pext, $gotTH); X local(%seen); X X Xpage: X foreach $page ( @pages ) { X next page if $page =~ /\.(BAK|OLD)$/i; X X if ($seen{$page}++) { X print "already saw $page\n" if $debug & 1; X next page; X } X X if (!open page) { X warn "couldn't open $page: $!\n"; X next page; X } X X $bingo = 1; # global var X X print "checking $page\n" if $debug & 1; X X $gotTH = 0; X $line = 0; X $sopage = ''; X Xline: while (<page>) { X print if $debug & 16; X next line if /^'''/ || /^\.\\"/; X X # deal with .so's on the first line. X # /usr/ucb/man uses this instead of links. X if (!($line++) && /^\.so\s+(.*)/) { X $sopage = $1; X print "$page -> $sopage\n" if $debug & 1; X ($basename = $sopage) =~ s%.*/%%; X if ($seen{$basename}++) { X print "already saw $basename\n" if $debug & 1; X next page; X } X if (!open(page,"../$sopage")) { X print "$page: cannot open $sopage: $!\n"; X next page; X } X $page = $basename; X next line; X } X X # check for internally consistent .TH line X if ( /^\.(TH|SC)/ ) { # SC is for mh X $gotTH++; X printf STDERR "TH checking %s", $_ if $debug & 4; X do flush(); X s/"+//g; X ($junk, $pname, $pext) = split; X if (¯o($pname)) { X printf STDERR "%s: can't resolve troff macro in .TH: %s\n", X $page, $pname; X next line; X } X $pext =~ y/A-Z/a-z/; X $pname =~ s/\\-/-/g; X $pname =~ y/A-Z/a-z/ if $pname =~ /^[\$0-9A-Z_\055]+$/; X ($pexpr = $page) =~ s/([.+])/\\$1/g; X $pexpr =~ s%.*/%%; X if ( "$pname.$pext" !~ /^$pexpr$/i) { X printf "%s: thinks it's in %s(%s)\n", X $page, $pname, $pext; X } X next line; X } X X next line unless /^\.S[Hh]\s+"*SEE ALSO"*/ X || /^\.S[Hh]\s+REFERENCES/ # damn posix X || /^\.Sa\s*$/; # damn mh X X # finally found the cross-references Xxref: while (<page>) { X print if $debug & 16; X last line if /^\.(S[Hh]|Co|Hi|Bu)/; # i really hate mh macros X next xref unless /\(/; X next xref if /^.PP/; X chop; X s/\\f[RIPB]//g; X s/\\\|//g; X s/\\-/-/g; Xentry: foreach $entry ( split(/,/) ) { X #print "got entry $entry\n"; X next entry unless $entry =~ /\(.*\)/; X $pname = ''; $pext = ''; X $1 = ''; $2 = ''; X ($pname, $pext) = X ($entry =~ /([A-Za-z0-9\$._\-]+)\s*\(([^)]+)\).*$/); X if ($debug & 8) { X printf STDERR "entry was %s, pname is %s, pext is %s\n", X $entry, $pname, $pext; X } X if (¯o($pname)) { X printf "%s: can't resolve troff macro in SEE ALSO: %s\n", X $page, $pname; X next entry; X } X next entry if !$pname || !$pext || $pext !~ /^\w+$/; X $pext =~ y/A-Z/a-z/; X $pname =~ y/A-Z/a-z/ if $pname =~ /^[A-Z_0-9\-]+$/; X #($psect = $pext) =~ s/^(.).*/$1/; X do check_xref($page,$pname,$pext); X X } # entry: foreach $entry ( split(/,/) ) X } # xref: while (<page>) X } # line: while (<page>) X printf "%s: missing .TH\n", $page if (!$gotTH); X } # page: foreach $page ( @pages ) X} # sub read_manapages X X X########################################################################### X# X# check_xref() X# X# given the name of the page we're looking for, check for a X# cross reference of a given man page and its assumed subsection X# X########################################################################### X Xsub check_xref { X local ($name, $target, $section) = @_; X local ($basesec, $subsec, $newsec ); X X printf STDERR " xref of %s(%s)\n", $target, $section if $debug & 2; X X return if &pathcheck($target,$section); X X X # if we get this far, something's wrong, so begin notify X printf "%s: %s(%s)", $name, $target, $section; X X ($basesec, $subsec) = ($section =~ /^(\d)(.*)$/); X X if ($name =~ /\.\d*([nlp])$/ && ($section == 1 || $section == 8) X && ($newsec = &pathcheck($target,$1))) { # hack for manl idiocy X &really($target,$newsec); X return; X } X X # first check if page.Xn is really in page.X X if ( $subsec && ($newsec = &pathcheck($target,$basesec))) { X &really($target,$newsec); X return; X } X X if ( $basesec == 1 && &pathcheck($target,8)) { X &really($target,8); X return; X } X X if ( $basesec == 8 && &pathcheck($target,1)) { X &really($target,1); X return; X } X X # maybe it thinks it's in 8 but got erroneously in 1 X if ( $basesec =~ /[18]/ && ($newsec = &pathcheck($target,'l'))) { X &really($target,$newsec); X return; X } X X # maybe page.X is really in page.Xn; this is expensive X if ( !$subsec && ($newsec = &pathcheck($target,$basesec.'*'))) { X &really($target,$newsec); X return; X } X X printf " missing\n"; X do flush(); X} X X########################################################################### X# X# pathcheck() X# X# takes a name (like 'tty') and a section (like '1d') X# and looks for 'tty.1d' first in the current root, X# then in all other elements of @xrefpath. the section X# may have a meta-character in it (like '8*'). X# X# returns the subsection in which we found the page, or X# null if we failed. X# X########################################################################### X Xsub pathcheck { X local ( $name, $section ) = @_; X local ( $basesec, $metasec, $fullpath, @expansion, $tree, %checked ); X local ( $return ) = 0; X X $metasec = &has_meta($section); X X ($basesec) = ($section =~ /^(.)/); X X foreach $tree ( $rootdir, @xrefpath ) { X next if !$tree || $checked{$tree}++; # only check each tree once X X $fullpath = "$tree/man$basesec/$name.$section"; X X print " testing $fullpath\n" if $debug & 8; X X if (!$metasec) { X if (-e $fullpath) { X $return = $section; X } X } else { X if ($use_DBM && &dbmopen($tree)) { X next; X } X open(SAVERR, '>&STDERR'); # csh globbing brain damage X close STDERR; X if ((@expansion = <${fullpath}>) && !&has_meta($expansion[0])) { X # redundant meta check due to sh brain-damage X #for (@expansion) { s/.*\.//; } X #$section = join(' or ',@expansion); X ($section) = ($expansion[0] =~ /([^.]+)$/); X $return = $section; X } X open(STDERR, '>&SAVERR'); # csh globbing brain damage X close SAVERR;
tchrist@convex.COM (Tom Christiansen) (01/08/91)
X } X } X printf STDERR " pathcheck returns $section\n" if $debug & 8; X $return; X} X X#--------------------------------------------------------------------------- X Xsub flush { X $| = 1; X print ''; X $| = 0; X} X Xsub has_meta { X $_[0] =~ /[[*?]/; X} X Xsub macro { X @_[0] =~ /^\\\*\(/; X} X Xsub really { X local($was,$is) = @_; X print " really in $was($is)\n"; X} X Xsub usage { X die "usage: $iam [-d debug-level] [-s sub-sections] [-p manpath] X [-x xrefpath] [pattern ...] \n"; X} X Xsub glob { X local($expr) = @_; X local(@retlist) = (); X local(*METADIR); # paranoia X X die "glob: null expr" unless $expr; # assert X X if ($expr =~ /\//) { X warn "glob: \"$expr\" has slashes, punting..."; X return <${expr}>; X } X X $expr =~ s/\*/.*/g; X $expr =~ s/\?/./g; X X unless (opendir(METADIR, '.')) { X warn "glob: can't opendir ".": $!\n"; X } else { X @retlist = sort grep(/$expr/o, grep(!/^\./, readdir(METADIR))); X closedir METADIR; X } X return @retlist; X} X Xsub dbmopen { X local($tree) = $_[0]; X # globals: %dbmopened, %warned X return 1 if $dbmopened{$tree}; X X unless (-f " X X} SHAR_EOF if test 10347 -ne "`wc -c < 'cfman'`" then echo shar: "error transmitting 'cfman'" '(should have been 10347 characters)' fi chmod 775 'cfman' fi echo shar: "extracting 'cfman.8l'" '(6219 characters)' if test -f 'cfman.8l' then echo shar: "will not over-write existing file 'cfman.8l'" else sed 's/^ X//' << \SHAR_EOF > 'cfman.8l' X.TH CFMAN 8L "" "15 November 1989" X.de Sh X.br X.PP X.ne 4 X.ti -.5i X\fB\\$1\fR X.PP X.. X.de LB \" little and bold X.ft B X.if !"\\$1"" \&\s-1\\$1\s+1 \\$2 \\$3 \\$4 \\$5 \\$6 X.ft R X.. X.de Sp X.if t .sp .5v X.if n .sp X.. X.ds lq \&"\" X.ds rq \&"\" X.if t \ X. ds lq `` X.if t \ X. ds rq '' X.de Q X\*(lq\\$1\*(rq\\$2 X.. X.Sh NAME Xcfman \- cross-reference man pages for internal consistency X.Sh SYNOPSIS X.B cfman X[ X.B \-d Xlevel X] X[ X.B \-s Xsections X] X[ X.B \-p Xmanpath X] X[ X.B \-x Xxrefpath X] X[ pattern | pathname ] ... X.br X.Sh DESCRIPTION X.I XCfman Xis a X.I perl Xprogram that checks that man page sources Xare mutually consistent in their X.LB "SEE ALSO" Xreferences. XIt will also report any X.LB ".TH" Xline that claims the Xman page is in a different place than X.I cfman Xfound it. X.PP XWhen supplied with no arguments, X.I cfman Xwill check all files (matching *.*) it finds in each man directory in Xyour colon-delimited X.LB "$MANPATH" Xenvariable if set, or in X.I /usr/man Xotherwise. It first verifies that the X.LB ".TH" Xsays Xthe man page is really where it should be, e.g. if the Xline is X.br X.in +.5i X.nf X\f(TA X\&.TH\ \ WIDGET\ \ 4 X.in -.5i X\fR X.fi X.br Xthen \fIwidget.8\fP should be the filename currently Xbeing examined. All upper-case will map to all lower-case, Xbut mixed case will be preserved for compatibility with Xthe X.LB X11 Xman pages. X.PP X.I Cfman Xthen skips ahead to the X.LB "SEE ALSO" Xsection and retrieves Xall comma-delimited entries of the general Xform \fIpagename(section)\fP. It first looks in the file X\&../man\fIsection/pagename.section\fP. If this fails Xand the current file ended in one of \fB[npl]\fP, but the X.I section Xreferenced is either X\fB1\fP or \fB8\fP, then it will check in X.I ../man8. XFailing this, X.I cfman Xchecks to see whether the referenced man page has been Xinstalled stripped of its subsection, e.g. \fIuucp\fP(1c) Xhas found its way into \fIuucp\fP(1). It then checks Xto see whether something in section \fB1\fP has been mis-installed Xin section \fB8\fP, or vice versa, or either one in section \fBl\fP Xmis-installed in the Xin section \fB8\fP and vice-versa. If all else fails, X.I cfman Xwill guess that a man page is referenced without its Xproper subsection, as in a reference to \fIrcp(1)\fP Xthat should really have been to \fIrcp(1c)\fP. If it finds Xthe misplaced man page, it reports where the reference Xthought it was and where it really was. Otherwise it Xreports the man page as missing. X.PP XThe X.LB $MANPATH Xvariable may be overridden by Xthe \fB-p\fP option. XAll checks will Xbe performed across each subtree specified in the manpath X(either from the environment of the command line), Xunless altered with the \fB-x\fP option. As a short-cut, Xthe \fIxrefpath\fP may have a leading colon to indicate Xthat it is to be concatenation of the \fImanpath\fP Xand the supplied \fIxrefpath\fP. X.PP XYou can restrict the sections checked with the \fB-s\fP Xswitch. By default, sections 1 through 8 will be examined. XThe section may be a shell metacharacter expression, Xlike X.Q ? Xor X.Q [18lpn] . X.PP XYou may restrict the individual man pages cross-referenced Xby specifying which ones you're interested in on the command Xline. These may be full pathnames, simple names like X.Q tty , Xor a shell metacharacter expression like X.Q *net . XIf Xno period occurs in the simple name, it is assumed to mean that Xthe name may have any extension. If you list specific Xman pages on the command line and X.I cfman Xfinds none matching your specification, it will report this fact. XSee the X.LB "EXAMPLES" Xsection. X.PP XMan pages that are linked by placing a \fB.so\fP directive Xon the first line will be correctly followed, and no man page Xin the same subtree. Very limited support for alternate Xman macros is provided: the X.I "\fIRand MH Message Handling System\fP" 's Xman macro set are recognized, as is Larry Wall's X.LB .Sh Xreplacement for X.LB .SH. X.Sh DIAGNOSTICS XRequires X.I perl Xto be at least version 3.0, patchlevel 1 to run. The Xprogram will abort if you try to run it with an Xearlier version of perl X.PP XFive different tracing levels can be specified with the \fB-d\fP Xoption. If any debugging is turned on, the walk through Xthe different components of the manpath are traced. XDebug values are numeric and additive, and are interpreted Xthis way: X.Sp X.in +.5i X.nf X.ne 5 X 1 Trace each man page examined X 2 Trace each cross reference examined X 4 Trace each \s-1\fB.TH\s+1\fP check X 8 Trace each file-existence test X 16 Trace each line X'in -.5i X.fi X.Sp XTracing information and other warnings are printed to X\fIstderr\fP, but normal messages about bad cross references Xare printed to \fIstdout\fP as that is \fIcfman\fP's principle Xtask. X.PP XEmbedded X.I troff Xstring macros starting \e*( cannot be resolved, and they Xwill trigger a warning message if found in the X.LB .TH Xor X.LB "SEE ALSO" Xsections. X.Sh EXAMPLES X.nf X\f(TA Xcfman # do all in $MANPATH Xcfman -p /export/exec/sun3/share/man # sun man pages Xcfman -p $HOME/man:/usr/local/mh/man:/usr/local/man:/usr/man Xcfman -p /usr/local/man -x :/usr/man # xref also in /usr/man Xcfman -s 18nlp # only these sections Xcfman '*tty*' fubar # check for *tty*.* and fubar.* Xcfman `pwd`/*.[1-8] # just check these files Xcfman -s 23 'sys*' # sys*.* files in sections 2,3 Xcfman -s 1 -p /export/exec/sun3/share/man X.fi X\fR X.PP XThe last command produced this output on my machine: X.nf X\f(TA Xbanner.1v: thinks it's in banner(1) Xfoption.1: skyversion(8) missing Xfrom.1: prmail(1) missing Xmake.1: rstat(8c) missing Xman.1: apropos(1) missing Xold-perfmon.1: missing .TH Xoldperfmon.1: missing .TH Xoldsetkeys.1: thinks it's in setkeys(1) Xorganizer.1: restore(1v) really in restore(8) Xsunview.1: traffic(1) really in traffic(1c) Xsort.1v: thinks it's in sort(1) Xsum.1v: thinks it's in sum(1) X.fi X\fR X.Sh ENVIRONMENT XThe default manpath will be taken from X.LB $MANPATH Xif set. X.Sh "SEE ALSO" Xman(1), troff(1), perl(1), man(7). X.Sh BUGS XDue to the current implentation of globbing in X.I perl, Xyou can get X.Q "Arguments too long" Xerrors. The workaround is to run X.I cfman Xfirst on X.Q [a-m]* , Xand then on X.Q [n-z]* . X.Sh AUTHOR XTom Christiansen, \s-1CONVEX\s+1 Computer Corporation. SHAR_EOF if test 6219 -ne "`wc -c < 'cfman.8l'`" then echo shar: "error transmitting 'cfman.8l'" '(should have been 6219 characters)' fi chmod 664 'cfman.8l' fi echo shar: "extracting 'makewhatis'" '(11184 characters)' if test -f 'makewhatis' then echo shar: "will not over-write existing file 'makewhatis'" else sed 's/^ X//' << \SHAR_EOF > 'makewhatis' X#!/usr/local/bin/perl X# X# makewhatis: perl rewrite for makewhatis X# author: tom christiansen <tchrist@convex.com> X# X# Copyright 1990 Convex Computer Corporation. X# All rights reserved. X Xeval "exec /usr/bin/perl -S $0 $*" # some bozo called us with 'sh foo' X if $running_under_some_shell; # 'catman -w' likes to do this; sigh X X X&source('stat.pl'); X X($program = $0) =~ s,.*/,,; X X$UNCOMPRESS = "uncompress"; X X$MAXWHATISLEN = 300; X$MAXDATUM = 1024; # DBM is such a pain X Xumask 022; X X&source('getopts.pl'); X Xdo Getopts('ynvdP:M:') || &usage; X X$opt_P = shift if $#ARGV >= 0; X X&usage if $#ARGV > -1; X Xsub usage { die "usage: $program [-n] [-y] [-v] [[-M] manpath]\n"; } X X$nflag = $opt_n; X$yflag = $opt_y; X X$manpath = $opt_M if $opt_M; X$manpath = $opt_P if $opt_P; # backwards contemptibility X$manpath = "/usr/man" unless $manpath; X@manpath = split(/:/,$manpath); X X$| = $debug = ($opt_d || $opt_v); X X$SIG{'INT'} = 'CLEANUP'; X$SIG{'TERM'} = 'CLEANUP'; X X$SIG{'HUP'} = 'INGORE'; X Xchop($cwd = `pwd`); X X$WHATIS = "whatis"; X X# --------------------------------------------------------------------------- X# main loop X# X# chdir to each root in man path. save mtime of dbase for later compares X# with files in case of nflag or yflag. X# --------------------------------------------------------------------------- X Xforeach $root ( @manpath ) { X local($dbtime, $filecount, $entries); X X $root = "$cwd/$root" if $root !~ m:^/:; # normalize to fullpathname X chdir $root || die "can't chdir to $root: $!"; X X print "root to $root\n" if $debug; X X if ($nflag || $yflag) { X unless (&Stat('whatis.pag')) { X print "couldn't stat $root/whatis DBM file\n" if $debug; X &rebuild(0, 0) if $yflag; X next; X } X $dbtime = $st_mtime; X } X &rebuild($nflag, $yflag); X} X Xexit $status; X X# --------------------------------------------------------------------------- X# rebuild -- open a new whatis database, store all references in files in X# this root to it. if dont_touch or test_stale parms set, just X# do the checks. if test_stale, recurse on a real rebuild. X# --------------------------------------------------------------------------- X Xsub rebuild { X local($dont_touch, $test_stale) = @_; X X local(%seen); # {dev,ino} pairs of files seen X local(%so); # the .so references seen X local(@WHATIS); # whatis list X local($entries, $filecount) = (0,0); X X unless ($dont_touch || $test_stale) { X if (!open (WHATIS, "> $WHATIS.$$")) { X warn "can't open $root/$WHATIS.$$: $!\n"; X $status = 1; X return;; X } X if (!dbmopen(WHATIS, "$WHATIS.$$", 0644)) { X warn "can't dbmopen $root/$WHATIS: $!\n"; X $status = 1; X return; X } X } X X foreach $mandir ( <man?*> ) { X next if $mandir =~ /man0.*/; X next if $mandir =~ /\.(old|bak)$/i; X next if $mandir =~ /~$/; X next unless -d $mandir; X X if (!chdir $mandir) { X warn "can't chdir to $root/$mandir: $!\n"; X next; X } X X ($dirext) = $mandir =~ /man(.*)$/; X $dirext =~ s/\.Z$//; X X print "subdir is $mandir\n" if $debug; X X if (!opendir(mandir,'.')) { X warn "can't opendir('$root/$mandir'): $!\n"; X next; X } X X # read each file in directory. use readdir not globbing X # because we don't want to blow up on huge directories XFILE: while ($FILE = readdir(mandir)) { X $compressed = $mandir =~ m:.*\.Z:; X next FILE if $FILE =~ /^\.{1,2}/; X X if ($FILE !~ /\S\.\S/) { X print STDERR "Skipping non-man file: $FILE\n"; X next FILE; X } X X # this will be optimized into a case statement X if ($FILE =~ /\.old$/i) { X next; X } elsif ($FILE =~ /\.bak$/i) { X next; X } elsif ($FILE =~ /\.out$/i) { X next; X } elsif ($FILE =~ /~$/) { X next; X } X X ($tmpfile = $FILE) =~ s/\.Z$//; X X ($filenam, $filext) = X $tmpfile =~ /^(\S+)\.([^.]+)$/; X X #if ($filext eq '.Z') { X #($filenam, $filext) = $filenam =~ /^(\S+)\.([^.]+)(\.Z)?$/; X #} X X if ($filext !~ /^${dirext}.*/ && $mandir ne 'mano') { X print STDERR "$FILE has a funny extension ($filext) to be in $mandir\n"; X } X X unless (&Stat($FILE)) { X warn "can't stat $root/$mandir/$FILE: $!\n"; X next FILE; X } X X if ($dont_touch || $test_stale) { X next unless $st_mtime > $dbtime; X print "$root/$mandir/$FILE newer than its dbm whatis file\n"; X closedir mandir; X chdir $root; X &rebuild(0,0) if $test_stale; X return; X } X X if ($apage = $seen{$st_dev,$st_ino}) { X printf "already saw %s, linked to %s\n", $FILE, $apage X if $debug; X &chopext($page = $FILE); X unless ($WHATIS{$page}) { X print "forgot $page\n" if $debug; X &store_indirect($page, $apage); X } X next FILE; X } X $seen{$st_dev,$st_ino} = $FILE; X X $compressed |= $FILE =~ /\.Z$/; X X if (!open(FILE, $compressed ? "$UNCOMPRESS < $FILE |" : $FILE)) { X warn "can't open $FILE: $!\n"; X next FILE; X } X X $filecount++; X print "opened $root/$mandir/$FILE\n" if $debug; X X &extract_names; X } X closedir mandir; X chdir $root || die "can't chdir back to $root: $!"; X } X X unless ($dont_touch || $test_stale) { X $, = "\n"; X print WHATIS (sort @WHATIS),''; X $, = ''; X close WHATIS || warn "can't close $WHATIS.$$: $!"; X rename ("$WHATIS.$$", $WHATIS) X || warn "can't rename $WHATIS.$$ to $WHATIS: $!"; X &check_sos(); X dbmclose(WHATIS) || warn "can't dbmclose $WHATIS: $!"; X for $ext ( 'pag', 'dir' ) { X unlink "$WHATIS.$ext"; X rename("$WHATIS.$$.$ext", "$WHATIS.$ext") X || warn "can't rename $WHATIS.$$.$ext: $!"; X } X print "$program: $root: found $entries entries in $filecount files\n"; X } X} X X X# in case we get interrupted X# Xsub CLEANUP { X print stderr "<<INTERRUPTED>> reading $FILE\n"; X chdir $root; X unlink "$WHATIS.$$", "$WHATIS.$$.pag", "$WHATIS.$$.dir"; X exit 1; X} X X# get next line from FILE, honoring escaped newlines X# Xsub getline { X local ($_); X X $_ = <FILE>; X { X chop; X if (/\\$/) { X chop; X $_ .= ' '; X $_ .= <FILE>; X redo; X } X } X $_; X} X Xsub extract_names { X local($_); X local($needcmdlist) = 0; X local($foundname) = 0; X local(@lines); X local($page, $page2, $indirect, $foundname, @lines, $nameline); X local($cmdlist, $ocmdlist, $tmpfile, $section); X local($prototype, $seenpage); X X unless (-T FILE) { X print STDERR "$FILE: not a text file\n"; X next; X } X X X $_ = <FILE>; # first check for leading .so reference X if (/^\.so\s+(man.+\/\S+)/) { X local($indirect, $indirect2); X $indirect = $1; X ($page) = $FILE =~ m:([^.]+)\.[^.]*$:; X ($page2) = $indirect =~ m:.*/([^/]+)$:; X ($indirect2 = $indirect) =~ s!/!.Z/!; X if (-e "../$indirect" || -e "../$indirect.Z" || -e $indirect2) { X $so{$page} = $page2; X print "$FILE: .so alias for $indirect\n" if $debug; X } else { X print STDERR "$FILE .so references non-existent $indirect\n"; X } X return; X } else { X /^\.TH\s+(\S*)\s+(\S+)/ && &doTH($1, $2); X } X XLINE: while (<FILE>) { X /^\.TH\s+(\S*)\s+(\S+)/ && &doTH($1, $2); X next LINE unless /^\.SH\s+"?NAME"?/i || /^\.NA\s?/; X $foundname = 1; X @lines = (); X $nameline = ''; XNAME: while ($_ = &getline()) { X last NAME if /^\.(S[hHYS])\s?/; # MH support X if ( $_ eq '.br' ) { X push(@lines, $nameline) if $nameline; X $nameline = ''; X next NAME; X } X s/^\.[IB]\b//; # Kill Bold and Italics X next if /^\./; X $nameline .= ' ' if $nameline; X $nameline .= $_; X } X X push(@lines, $nameline); X X for ( @lines ) { X next unless ord; X s/\\f([PBIR]|\(..)//g; # kill font changes X s/\\s[+-]?\d+//g; # kill point changes X s/\\&//g; # and \& X s/\\\((ru|ul)/_/g; # xlate to '_' X s/\\\((mi|hy|em)/-/g; # xlate to '-' X s/\\\*\(..//g && # no troff strings X print STDERR "trimmed troff string macro in NAME section of $FILE\n"; X s/\\//g; # kill all remaining backslashes X s/^\.\\"\s*//; # comments X if (!/\s+-+\s+/) { X # ^ otherwise L-devices would be L X printf STDERR "$FILE: no separated dash in \"%s\"\n", $_; X $needcmdlist = 1; # forgive their braindamage X s/.*-//; X $desc = $_; X } else { X ($cmdlist, $desc) = ( $`, $' ); X $cmdlist =~ s/^\s+//; X } X X # need this for two reasons: sprintf might blow up and so X # might the dbm store due to 1k limit X # X $ocmdlist = $cmdlist; # before truncation X if (length($cmdlist) > $MAXWHATISLEN) { X printf STDERR "$FILE: truncating cmdlist from %d to %d bytes for DBM's sake\n", X length($cmdlist), $MAXWHATISLEN; X $cmdlist = substr($cmdlist,0,$MAXWHATISLEN) . "..."; X } X X ($tmpfile = $FILE) =~ s/\.Z$//; X ($page, $section) = $tmpfile =~ /^(\S+)\.(\S+)$/; X $cmdlist = $page if $needcmdlist; X X $prototype = ''; $seenpage = 0; X X foreach $cmd (split(/[\s,]+/,$ocmdlist)) { X next unless $cmd; X $seenpage |= ($cmd eq $page); X if (! $prototype) { X &store_direct($cmd, $cmdlist, $tmpfile, $dirext, $desc); X $prototype = $cmd; X } else { X &store_indirect($cmd, "$prototype.$filext"); X } X } X unless ($seenpage) { X print "$FILE: forgot my own name!\n" if $debug; X if ($prototype) { X &store_indirect($page, "$prototype.$filext"); X } else { X &store_direct($page, $page, $FILE, $dirext, ''); X } X } X } X } X unless ($foundname) { X print STDERR "$FILE: no NAME lines, so has no whatis description!\n"; X ($tmpfile = $FILE) =~ s/\.Z$//; X ($page, $section) = $tmpfile =~ /^(\S+)\.(\S+)$/; X &store_direct($page, $page, $tmpfile, $dirext, 'NO DESCRIPTION'); X } X} X X# -------------------------------------------------------------------------- Xsub source { X local($file) = @_; X local($return) = 0; X X X $return = do $file; X die "couldn't parse \"$file\": $@" if $@; X die "couldn't do \"$file\": $!" unless defined $return; X warn "couldn't run \"$file\"" unless $return; X} X X Xsub chopext { X $_[0] =~ s/\.[^.]+$//; X} X Xsub check_sos { X local($key); X X foreach $key (keys %so) { X unless (defined $WHATIS{$key}) { X printf STDERR X "%s was a .so alias for %s, but %s's NAME section doesn't know it!\n", X $key, $so{$key}, $so{$key}; X &store_indirect($key, $so{$key}); X } X } X} X Xsub store_direct { X local($cmd, $list, $page, $section, $desc) = @_; X local($datum); X X push(@WHATIS,sprintf("%-20s - %s", "$list ($filext)", $desc)); X X $datum = join("\001", $list, $page, $section, $desc); X X if (defined $WHATIS{$cmd}) { X if (length($WHATIS{$cmd}) + length($datum) + 1 > $MAXDATUM) { X print STDERR "can't store $page -- would break DBM\n"; X return; X } X $WHATIS{$cmd} .= "\002"; X } X X print "storing $cmd\n" if $debug; X $WHATIS{$cmd} .= $datum; X $entries++; X} X Xsub store_indirect { X local($indirect, $real) = @_; X X print "storing $indirect as reference to $real\n" X if $debug; X X $WHATIS{$indirect} .= "\002" if $WHATIS{$indirect}; X $WHATIS{$indirect} .= $real; X $entries++; X} X Xsub doTH { X local($THname, $THext) = @_; X local($int_name, $ext_name); X X ($int_name = "$THname.$THext") =~ tr/A-Z/a-z/; X ($ext_name = "$filenam.$filext") =~ tr/A-Z/a-z/; X X if ($int_name ne $ext_name && $debug) { X print STDERR "${FILE}'s .TH thinks it's in $int_name\n"; X } X} SHAR_EOF if test 11184 -ne "`wc -c < 'makewhatis'`" then echo shar: "error transmitting 'makewhatis'" '(should have been 11184 characters)' fi chmod 755 'makewhatis' fi echo shar: "extracting 'straycats'" '(476 characters)' if test -f 'straycats' then echo shar: "will not over-write existing file 'straycats'" else sed 's/^ X//' << \SHAR_EOF > 'straycats' X#!/usr/local/bin/perl X X$manpath = shift || $ENV{'MANPATH'} || '/usr/man/'; X Xfor $root (split(/:/, $manpath)) { X X chdir($root) || die "can't chdir to $root: $!\n"; X X foreach $catdir ( <cat*> ) { X opendir (catdir, $catdir) || die "can't opendir $dir: $!"; X ($mandir = $catdir) =~ s/cat/man/; X foreach $file ( readdir(catdir) ) { X next if $file eq '.' || $file eq '..'; X next if -e "$mandir/$file"; X print "no man page for $root/$catdir/$file\n"; X } X } X X} SHAR_EOF if test 476 -ne "`wc -c < 'straycats'`" then echo shar: "error transmitting 'straycats'" '(should have been 476 characters)' fi chmod 775 'straycats' fi echo shar: "extracting 'man.ms'" '(34978 characters)' if test -f 'man.ms' then echo shar: "will not over-write existing file 'man.ms'" else sed 's/^ X//' << \SHAR_EOF > 'man.ms' X.\" -------------------------------------------------------- X.de CW \" macro to begin constant-width font X\" I like TA better, but whatever looks nicest should be used X.ft TA X.. X.\" -------------------------------------------------------- X.de CE \" macro to end constant-width font X.ft R \" maybe should really be .ft P? X.. X.\" -------------------------------------------------------- X.de M \" man page reference X\\fI\\$1\\fR\\|(\\$2\)\\$3 X.. X.\" -------------------------------------------------------- X.\" Here begins a mostly successful attempt at X.\" defining a macro to output a boxed, centered X.\" figure that includes an auto-increment figure #N X.\" line using -ms macros. X.\" It doesn't actually do the centering. sigh. X.nr FN 0 1 X.de BF \" begin figure X.ds FN \\$1 X.KF X.nf X.na X.sp X.B1 X.CW X.. X.\" -------------------------------------------------------- X.de EF \" end figure X.sp .5v X.B2 X.CE X.ce X\\fBFigure \\n+(FN \\(em \\*(FN\\fR X.sp X.fi X.ad X.KE X.. X.\" -------------------------------------------------------- X.de LB \" little and bold X.ft B X.if !"\\$1"" \&\s-1\\$1\s+1 \\$2 \\$3 \\$4 \\$5 \\$6 X.ft R X.. X.\" End macro definitions X.\" -------------------------------------------------------- X.TL X\s+2The Answer to All Man's Problems\s-2 X.AU X\s+2\fITom Christiansen\fP\s-2 X.AI X\s-1CONVEX\s+1 Computer Corporation XPOB 833851 X3000 Waterview Parkway XRichardson, TX 75083-3851 X.sp X\fI{uunet,uiucdcs,sun}!convex!tchrist Xtchrist@convex.com\fP X.AB no X.ps -1 XThe \s-1UNIX\s0 on-line manual system was designed many years ago to suit Xthe needs of systems at the time, but Xdespite the growth in complexity of typical Xsystems and the need for more sophisticated software, Xfew modifications have been Xmade to it since then. XThis paper Xpresents the results of a complete rewrite of the man system. The Xthree principal goals were to effect substantial gains in Xfunctionality, extensibility, and speed. The secondary goal was to Xrewrite a basic \s-1UNIX\s0 utility in the perl programming language to Xobserve how perl affected development time, execution time, and design Xdecisions. X.PP X.ps -1 XExtensions to the original man system include storing the whatis Xdatabase in \s-1DBM\s0 format for quicker access, intelligent handling of Xentries with multiple names (via \fB.so\fP inclusion, links, or the \s-1NAME\s0 Xsection), embedded tbl and eqn directives, multiple man trees, Xextensible section naming possibilities, Xuser-definable section and sub-section search ordering, Xan indexing mechanism Xfor long man pages, Xtypesetting of man pages, Xtext-previewer support for bit mapped displays, Xautomatic validity checks on the \s-1SEE\s0 \s-1ALSO\s0 sections, Xsupport for compressed man pages to conserve disk usage, Xper-tree man macro definitions, Xand support for man pages for multiple Xarchitectures or software versions from the same host. X.ps +1 X.AE X.NH XIntroduction X.PP XThe \s-1UNIX\s0 on-line manual system was Xdesigned many years ago to suit the needs of the systems Xat the time. Since then, Xdespite Xthe growth in complexity of Xtypical systems and the need for more sophisticated software Xto support them, Xfew modifications of major significance Xhave been Xmade to the program. XThis paper describes problems inherent Xin earlier versions of the \fIman\fP program, proposes solutions Xto these problems, and outlines one implementation of these solutions. X.NH XThe Problem X.NH 2 XThe Monolithic Approach X.PP XOne of the most serious problems with the \fIman\fP program up to Xand including the \s-1BSD\s04.2 release was that Xall man pages on the entire system were expected to reside Xunder a common directory, X\fI/usr/man\fR. XThere was no Xnotion of separate sets of man pages installed on Xthe same machine in different subdirectories. XAt large installations, Xsituations commonly arise in which this Xfunctionality is desirable. XA site may wish to keep vendor-supplied man pages Xseparate from man pages that were developed locally Xor acquired from some third party. XAn individual or group may wish to maintain their own set Xof man pages. XMultiple versions of the same software package Xmight be simultaneously installed on the same machine. XA heterogeneous environment may want to be able to view man pages for Xall available architectures from any machine. XGiven the requirement that all man pages live in the Xsame directory, these scenarios are difficult to impossible to Xsupport. X.PP XThe \fIman\fP program distributed in the \s-1BSD\s04.3 release Xincluded the concept of a \s-1MANPATH\s0, Xa colon-delimited Xlist of complete man trees taken either from the user's Xenvironment or supplied on the command line. XWhile this was a vast improvement over the previous monolithic approach, Xseveral significant problems remained. XFor one thing, the program still had to use Xthe X.M access 2 Xsystem call on all possible paths to find out Xwhere the man page for a particular topic Xexisted. XWhen the user has a \s-1MANPATH\s0 containing multiple Xcomponents, the time needed for the \fIman\fP program to locate Xa man page is often noticeable, particularly when the target Xman page does not exist. X.NH 2 XHard-coded Section Names X.PP XAnother problem with the \fIman\fP program unresolved by Xthe \s-1BSD\s04.3 release Xwas that all possible sections in which a man page could Xreside were hard-coded Xinto the program. This means that while a X.B manp
tchrist@convex.COM (Tom Christiansen) (01/08/91)
Xsection would be recognized, a X.B manq Xdirectory would not be, and while a X.B man3f Xdirectory would be recognized, a X.B man3x11 Xdirectory would not be. X.PP XLikewise, the possible subsections Xfor a man page were also embedded in the source code, so Xa man page named something like X.I /usr/man/man3/XmLabel.3x11 Xwould not be found because X.B 3x11 Xwas not in the hard-coded list of viable subsections. XSome systems install all man pages stripped of subsection Xcomponents in the file name. This situation is less than optimal Xbecause it proves useful to be able Xto supply both a X.M getc 3f Xand a X.M getc 3s . XDistinguishing between subsections is Xparticularly convenient with the ``intro'' man pages; Xa vendor could supply X.M intro 3 X.M intro 3a , X.M intro 3c , X.M intro 3f , X.M intro 3m , X.M intro 3n , X.M intro 3r , X.M intro 3s , Xand X.M intro 3x Xas introductory man pages for the various libraries. XHowever, the task of running X.M access 2 Xon all possible subsections is slow and tedious, requiring Xrecompilation whenever a new subsection is invented. X.NH XReferences in the Filesystem X.PP XThe existing man system had no elegant way to handle Xman pages containing more than one entry. For example, X.M string 3 Xcontains references to X.M strcat 3 , X.M strcpy 3 , Xamongst others. Because the \fIman\fP program looks for Xentries only in the file system, these extra references must be Xrepresented as files that reference the base man page. The most Xcommon practice is to have a file consisting of Xa single line Xtelling X.I troff Xto source the other man page. XThis file would read something like: X.sp X.ti 5 X.CW X\&.so man3/string.3 X.CE X.sp XOccasionally, Xextra references are created with a link in the file Xsystem (either a hard link or a symbolic one). Except when Xusing Xhard links, this method wastes Xdisk blocks and inodes. In any case, Xthe directory gains more entries, slowing Xdown accesses to files in those directories. Logic Xmust be built into the \fIman\fP program to Xdetect these extra references. XIf not, when man pages are reformatted into their Xcat directories, separate formatted man pages are stored Xon disk, wasting substantial amounts of disk space Xon duplicate information. XOn systems with numerous man pages, the directories can grow Xso large that all man Xpages for a given section cannot be listed on the command line Xat one time because of kernel restrictions on the total length of the Xarguments to X.M exec 2 . XBecause of the need to store reference information Xin the file system, the problem is only made worse. XThis often happens in Xsection 3 after the man pages for the X Xlibrary have been installed, but Xcan occur in other sections as well. X.PP XThe X.M makewhatis 8 Xprogram is a Bourne shell script that generates the X.I /usr/lib/whatis Xindex, and is used by X.M apropos 1 Xand X.M whatis 1 Xto provide one-line summaries of man pages. These Xprograms are part of the X.I man Xsystem Xand are often links to each other and sometimes to X.I man Xitself. XIf any of Xthe man subdirectories contain more files than the shell Xcan successfully expand on the command line, the X.I makewhatis Xscript fails Xand no index is generated. When this occurs, X.I whatis Xand X.I apropos Xstop working. The X.M catman 8 Xprogram, used to pre-format raw man pages, suffers Xfrom the same problem. X.PP XOf course, X.I makewhatis Xwasn't working all that well, anyway. XIt was a wrapper around many calls to little programs Xthat each did a small piece of the work, making it Xrun slowly. XIt, too, had a hard-coded pathname for where man pages resided Xon disk and which sections were permitted. X.I Makewhatis Xdidn't always extract the proper information Xfrom the man page's \s-1NAME\s0 Xsection. When it did, this information was sometimes Xgarbled due to embedded X.I troff Xformatting information. XBut even garbled information was better Xthan none at all. XEven so, these programs left some things to be desired. X.I Apropos Xdidn't understand regular expression searches, and both Xit and X.I whatis Xpreferred to do their own lookups using basic, unoptimized C functions Xlike X.M index 3 Xrather than using a general-purpose optimized string search program Xlike X.M egrep 1 . X.NH XThe Solution X.NH 2 XA Real Database X.PP XThe problem in all these cases appeared to be that the filesystem Xwas being used as a database, and that this paradigm did not hold Xup well to expansion. Therefore the solution was to move Xthis information into a database for more rapid access. XUsing this database, X.I man Xand X.I whatis Xneed no longer call X.M access 2 Xto test all possible locations for the desired man page. XTo solve the other problems, X.M makewhatis 8 Xwould be recoded so it didn't rely on the shell Xfor looking at directories. X.NH 2 XCoding in Perl X.PP XWhen the project was first contemplated, the Xperl programming language by Larry Wall was rapidly Xgaining popularity as an alternative to C for tasks that Xwere either too slow when written as shell scripts, or Xsimply exceeded the shells' somewhat limited capabilities. XSince perl was Xoptimized for parsing text, had convenient X.M dbm 3x Xsupport built in to it, and the task really didn't seem complex Xenough to merit a full-blown treatment in C or C++, Xperl was selected as the language of choice. XHaving all code written in perl would also help support Xheterogeneous environments because the resulting scripts could Xbe copied and run on any hardware or software platform supporting Xperl. No recompilation would be required. X.PP XSome concern existed about choosing Xan interpreted language when one of the issues to address was Xthat of speed. It was decided to do the prototype in perl Xand, if necessary, translate this into C should performance Xprove unacceptable. X.PP XThe first task was to recode X.M makewhatis 8 Xto generate the new X.I whatis Xdatabase using \fIdbm\fP. The X.M directory 3 Xroutines were used rather than shell globbing to circumvent Xthe problem of large directories breaking shell wildcard Xexpansions. Perl proved to be an appropriate choice for this Xtype of text processing (see Figure 1). X.BF "\fImakewhatis\fP excerpt #1" Xs/\e\ef([PBIR]|\e(..)//g; # kill font changes Xs/\e\es[+-]?\ed+//g; # kill point changes Xs/\e\e&//g; # and \e& Xs/\e\e\e((ru|ul)/_/g; # xlate to '_' Xs/\e\e\e((mi|hy|em)/-/g; # xlate to '-' Xs/\e\e\e*\e(..//g && # no troff strings X print STDERR "trimmed troff string macro in NAME section of $FILE\en"; Xs/\e\e//g; # kill all remaining backslashes Xs/^\e.\e\e"\es*//; # kill comments Xif (!/\es+-+\es+/) { X # ^ otherwise L-devices would be L X print STDERR "$FILE: no separated dash in $_\en"; X $needcmdlist = 1; # forgive their braindamage X s/.*-//; X $desc = $_; X} else { X ($cmdlist, $desc) = ( $`, $' ); X $cmdlist =~ s/^\es+//; X} X.EF X.NH 2 XDatabase Format X.PP XThe database entries themselves are conveniently Xaccessed as arrays from perl. To save space and Xaccommodate man pages with multiple references, two Xkinds of database entries exist: direct and indirect. XIndirect entries are simply references to direct entries. XFor example, indirect entries for X.M getc 3s , X.M getchar 3s , X.M fgetc 3s , Xand X.M getw 3s Xall point to the real entry, which is X.M getc 3s . XIndirect entries are created for multiple entries in Xthe \s-1NAME\s0 section, for symbolic and hard links, and Xfor X.B \&.so Xreferences. Using the \s-1NAME\s0 section is the preferred Xmethod; the others are supported for backwards compatibility. X.PP X.ne 4 XAssuming that the \s-1WHATIS\s0 array has been bound to the Xappropriate X.I dbm Xfile, storing indirect entries is trivial: X.sp X.CW X.ti 1i X$WHATIS{'fgetc'} = 'getc.3s'; X.sp X.CE XWhen a program encounters an indirect entry, such as Xfor \fIfgetc\fP, it must make another lookup based on Xthe return value of first lookup (stripped of its Xtrailing extension) until it finds a direct entry. The Xtrailing extension is kept so that an indirect reference Xto X.M gtty 3c Xdoesn't accidentally pull out X.M stty 1 Xwhen it really wanted X.M stty 3c . X.PP XThe format of a direct entry is more complicated, because Xit needs to encode the description to be used by X.M whatis 1 Xas well as the section and subsection information. XIt can be distinguished from an indirect entry because Xit contains four fields delimited by control-A's (\s-1ASCII 001\s0), Xwhich are themselves prohibited from being in any Xof the fields. The fields are as follows: X.br X.in +5n X.IP 1 XList of references that point to this man page; this Xis usually everything to the left of the hyphen Xin the \s-1NAME\s0 section. X.IP 2 XRelative pathname of the file the man page is kept in; Xthis is stored for the indirect entries. X.IP 3 XTrailing component of the directory in which the Xman page can be found, such as X.B 3 Xfor \fBman3\fP. X.IP 4 XDescription of the man page for use by Xthe X.I whatis Xand X.I apropos Xprograms; basically everything to the right of the hyphen in the XN\s-1AME\s0 section. X.in -5n X.PP XAt first glance, the third field would Xseem redundant. It would appear that you could Xderive it from the character after the dot in the second field. XHowever, to support arbitrary subdirectories like X.B man3f Xor X\fBman3x11\fP, you must also know the name of the Xdirectory so you don't look in X.B man3 Xinstead. Additionally, a long-standing tradition exists Xof using the X.B mano Xsection Xto store old man pages from arbitrary sections. XFurthermore, man pages are sometimes installed in the Xwrong section. To support these scenarios, restrictions Xregarding the format of filenames used for man pages were Xrelaxed in \fIman\fR, X\fImakewhatis\fR, and \fIcatman\fR, Xbut warnings would be issued by X.I makewhatis Xfor man pages installed in directories that don't have Xthe same suffix as the man pages. X.NH 2 XMultiple References to the Same Topic X.PP XA problem arises from the fact that the same topic Xmay exist in more than one section of the manual. XWhen a lookup is performed on a topic, Xyou want to retrieve all possible man page locations Xfor that topic. The X.I whatis Xprogram wants to display them all to the user, while Xthe X.I man Xprogram will either show all the man pages X(if the X.B \-a Xflag is given) or Xsort what it has retrieved according to a particular section and Xsubsection precedence, by default showing entries from section X1 before those from section 2, and so forth. Therefore, Xeach lookup may actually return a list of direct and Xindirect lookups. This list is delimited by control-B's X(\s-1ASCII 002\s0), which are stripped from the data fields, should Xthey somehow contain any. The code for storing a direct entry Xin the X.I whatis Xdatabase is featured in Figure 2. X.BF "\fImakewhatis\fP excerpt #2" Xsub store_direct { X local($cmd, $list, $page, $section, $desc) = @_; # args X local($datum); X X $datum = join("\e001", $list, $page, $section, $desc); X X if (defined $WHATIS{$cmd}) { X if (length($WHATIS{$cmd}) + length($datum) + 1 > $MAXDATUM) { X print STDERR "can't store $page -- would break DBM\en"; X return; X } X $WHATIS{$cmd} .= "\e002"; # append separator X } X $WHATIS{$cmd} .= $datum; # append entry X} X.EF X.KE X.PP XNotice the check of the new datum's Xlength against the value of \s-1MAXDATUM.\s0 This is because of the Xinherent limitations in the implementation of the X.M dbm 3x Xroutines. This is 1k for X.I dbm Xand 4k for X.I ndbm . XThis restriction will be relaxed Xif a \fIdbm\fR-compatible set of routines is written without Xthese size limitations. The \s-1GNU\s0 X.I gdbm Xroutines hold promise, but they were released after the Xwriting of these programs and haven't been investigated yet. XIn practice, these limits are seldom if ever reached, especially Xwhen X.I ndbm Xis used. X.NH XOther Problems, Other Solutions X.PP XThe rewrite of X.I makewhatis , X.I catman , Xand X.I man Xto understand multiple man trees and to use a database Xfor topic-to-pathname mapping Xdid much to alleviate the most important problems Xin the existing man system, but several minor problems Xremained. Since this was a complete rewrite of the entire Xsystem, it seemed an appropriate time to address these as well. X.NH 2 XIndexing Long Pages X.PP XSeveral of the most frequently consulted man pages on the system Xhave grown beyond the scope of a quick reference guide, Xinstead filling the function of a detailed user manual. XMan pages of this sort include those for shells, window Xmanagers, Xgeneral purpose Xutilities such as awk and perl, Xand the \s-1X11\s0 man pages. XAlthough these man pages Xare internally organized into sections and subsections that Xare easily visible on a hard-copy printout, the on-line Xman system could not recognize these internal Xsections. Instead, the user was forced to search through pages Xof output looking for the section of the man page containing Xthe desired information. X.PPe XTo alleviate this time-consuming tedium, the man program Xwas taught to parse the X.I nroff Xsource for man pages in order to build up an index of these sections Xand present them to the user on demand. XSee Figure 3 for an excerpt from the X.M ksh 1 Xindex page, displayable via the new X.B \-i Xswitch. X.BF "\fIksh\fP index excerpt" XIdx Subsections in ksh.1 Lines X 1 NAME 3 X 2 SYNOPSIS 22 X 3 DESCRIPTION 15 X 4 Definitions. 43 X 5 Commands. 338 X 6 Comments. 6 X 7 Aliasing. 107 X 8 Tilde Substitution. 47 X 9 Command Substitution. 28 X10 Process Substitution. 49 X11 Parameter Substitution. 645 X12 Blank Interpretation. 15 X13 File Name Generation. 87 X.EF X.PP XThe X.I /usr/man/idx*/ Xdirectories Xserve the Xsame function for saved indices Xas X.I /usr/man/cat*/ Xdirectories do for saved formatted man pages. XThese are regenerated as needed according the Xthe same criteria used to regenerate the cat pages. XThey can be used to index into a given man page or Xto list a man page's subsections. XTo begin at a given subsection, the user appends Xthe desired subsection to the name of the man page Xon the command line, Xusing a forward slash as a delimiter. Alternatively, Xthe user can just supply a trailing slash on the man page Xname, in which case they are presented with the index listing Xlike the one the X.B \-i Xswitch provides, then prompted for the section Xin which they are interested. A double slash indicates Xan arbitrary regular expression, not a section name. XThis is merely a short-hand notation for first running Xman and then typing X.CW X/expr X.CE Xfrom within the user's pager. XSee Figure 4 Xfor example usages of the indexing features. X.BF "Index Examples" Xman -i ksh # show sections Xman ksh/ # show sections, prompt for which one X Xman ksh/tilde Xman ksh/8 # equivalent to preceding line X Xman ksh/file Xman ksh/generat # equivalent to preceding line Xman ksh/13 # so is this X Xman ksh//hangup # start at this string X.EF X.PP XThis indexing scheme is implemented by searching the index stored in X.I /usr/man/idx1/ksh.1 Xif it exists, or generated dynamically otherwise, Xfor the requested subsection. A numeric subsection is Xeasily handled. For strings, a case-insensitive Xpattern match is first Xmade anchored to the front of the string, then \(em failing Xthat \(em anywhere in the section description. This way Xthe user doesn't need to type the full section title. XThe X.I man Xprogram starts up the pager with a Xleading argument to begin at that section. Both X.M more 1 Xand X.M less 1 Xunderstand this particular notation. XIn the first Xexample given above, this would be X.sp X.CW X.ti +.5i Xless '+/^[ \et]*Tilde Substitution' /usr/man/cat1/ksh.1 X.sp X.CE X.PP XOnce again, perl proved Xuseful for coding this algorithm concisely. The Xsubroutine for doing this is given in XFigure 5. Given an expression such as ``5'' Xor ``tilde'' or ``file'' and a pathname of the man Xpage, X.I man Xloads Xan array of subsection Xindex titles and quickly retrieves the proper Xheader to pass on to the pager. Perl's built-in X.B grep Xroutine for selecting from arrays those elements Xconforming to certain criteria made the coding easy. X.BF "Locate Subsection by Index" Xsub find_index { X local($expr, $path) = @_; # subroutine args X local(@matches, @ssindex); X @ssindex = &load_index($path); X X if ($expr > 0) { # test for numeric section X return $ssindex[$expr]; X } else { X if (@matches = grep (/^$expr/i, @ssindex)) { X return $matches[0]; X } elsif (@matches = grep (/$expr/i, @ssindex)) { X return $matches[0]; X } else { X return ''; X } X } X} X.EF X.NH 2 XConditional Tbl and Eqn Inclusion X.PP XSeveral other relatively minor enhancements were made Xto the man system in the course of its rewrite. XOne of these Xwas to include calls to X.M eqn 1 Xand X.M tbl 1 Xwhere appropriate. For instance, the \s-1X11\s0 man pages use X.I tbl Xdirectives to construct a number of tables. XIt was not sufficient to supply Xthese extra filters for all man pages. Besides the Xslight performance degradation this would incur, a Xmore serious problem exists: some systems have man pages that Xcontain embedded X.LB .TS Xand X.LB .TE Xdirectives; however, the data between them was not X.I tbl Xinput, but rather its output. They have already Xbeen pre-processed in the unformatted versions. XTo do so again causes X.I tbl Xto complain bitterly, so heuristics to check for this condition Xwere built in to the function that determines which filters Xare needed. X.PP XTo support tables and equations in man pages when viewed on-line, Xthe output must be run through X.M col 1 Xto be legible. Unfortunately, this strips the man pages Xof any bold font changes, which is undesirable because it is Xoften important to distinguish between bold and italics for Xclarity. Therefore, before the formatted man page is fed to X\fIcol\fP, all text in bold (between escape sequences) Xis converted to character-backspace-character combinations. These Xcombinations Xcan be recognized by the user's pager as a character in Xa bold font, just as underbar-backspace-character is recognized Xas an italic (or underlined) one. Unfortunately, while X.I less Xdoes recognize this convention, X.I more Xdoes not. By storing the formatted versions with all escape-sequences Xremoved, the user's pager can be invoked without a pipe to X.I ul Xor X.I col Xto fix the reverse line motion directives. This provides the pager with Xa handle on the pathname of the cat page, allowing users to back up Xto the start of man pages, even exceptionally long ones, without exiting the X.I man Xprogram. This would not be feasible if the pager were being fed Xfrom a pipe. X.NH 2 XTroffing and Previewing Man Pages X.PP XNow that many sites have high-quality laser printers Xand bit-mapped displays, it seemed desirable for X.I man Xto understand how to direct X.I troff Xoutput to these. A new option, \fB-t\fR, Xwas added to mean that X.I troff Xshould be used instead of X\fInroff\fR. XThis way users can easily get pretty-printed versions of Xtheir man pages. X.PP XFor workstation or X-terminal users, X.I man Xwill recognize Xa \s-1TROFF\s0 environment variable or Xcommand line argument to indicate an Xalternate program to use for typesetting. X(This presumes that the program recognizes X.I troff Xoptions.) This method often produces more legible output Xthan X.I nroff Xwould, allows the user to stay in their office, and saves Xtrees as well. X.NH 2 XSection Ordering X.PP XThe same topic can occur in more than one section of Xthe manual, but Xnot all users on the system want the same default Xsection ordering that X.I man Xuses to sort these possible pages. XFor instance, XC programmers who want to look up the man page for X.M sleep 3 Xor X.M stty 3 Xfind that by default, X.I man Xgives them X.M sleep 1 Xand X.M stty 1 Xinstead. A \s-1FORTRAN\s0 programmer may want to see X.M system 3f , Xbut instead gets X.M system 3 . XTo accommodate these needs, the X.I man Xprogram will honor a \s-1MANSECT\s0 environment Xvariable (or a X.B \-S Xcommand line switch) containing a list of section suffixes. XIf subsection or multi-character section ordering Xis desired, this string should be colon-delimited. XThe default ordering is ``ln16823457po''. XA C programmer might set his \s-1MANSECT\s0 to be ``231'' instead to access Xsubroutines and system calls before commands of the same name. XA \s-1FORTRAN\s0 programmer might prefer ``3f:2:3:1'' to get Xat the \s-1FORTRAN\s0 versions of subroutines before the standard XC versions. XSections absent from the \s-1MANSECT\s0 have a sorting priority Xlower than any that are present. X.NH 2 XCompressed Man Pages X.PP XBecause man pages are \s-1ASCII\s0 text files, they stand to benefit from Xbeing run through the X.M compress 1 Xprogram. XCompressing man pages Xtypically yields disk space savings of around 60%. XThe start-up time for decompressing the man page when Xviewing is not enough to be bothersome. However, running X.I makewhatis Xacross compressed man pages takes significantly longer Xthan running it over uncompressed ones, so some sites may wish to Xkeep only the formatted pages compressed, not the unformatted Xones. X.PP XTwo different Xways of indicating compressed man pages seem to exist Xtoday. One is where the man page itself has an attached X.B .Z Xsuffix, yielding pathnames like X\fI/usr/man/man1/who.1.Z\fR. XThe other way is to have Xthe section directory contain the X.B .Z Xsuffix Xand have the files named normally, as in X\fI/usr/man/man1.Z/who.1\fR. XEither strategy is supported to ease porting Xthe program to other systems. XAll programs dealing with man pages have been updated to Xunderstand man pages stored in compressed form. X.NH 2 XAutomated Consistency Checking X.PP XAfter receiving a half-dozen or so bug reports regarding Xnon-existent man pages referenced in \s-1SEE\s0 \s-1ALSO\s0 sections, Xit became apparent that the only way to verify that all Xbugs of this nature had really been expurgated would be to automate the process. XThe X.I cfman Xprogram Xverifies that man pages Xare mutually consistent in their \s-1SEE\s0 \s-1ALSO\s0 references. It Xalso reports man pages whose X.LB .TH Xline claims the man page is in Xa different place than X.I cfman Xfound it. X.I Cfman Xcan locate man pages Xthat are improperly referenced rather than merely missing. It Xcan be run on an entire man tree, or on individual files as Xan aid to developers writing new man pages. X.BF "Sample \fIcfman\fP run" Xat.1: cron(8) really in cron(1) Xbinmail.1: xsend(1) missing Xdbadd.1: dbm(3) really in dbm(3x) Xksh.1: exec(2) missing Xksh.1: signal(2) missing Xksh.1: ulimit(2) missing Xksh.1: rand(3) really in rand(3c) Xksh.1: profile(5) missing Xld.1: fc(1) really in fc(1f) Xsccstorcs.1: thinks it's in ci(1) Xuuencode.1c: atob(n) missing Xyppasswd.1: mkpasswd(5) missing Xfstream.3: thinks it's in fstream(3c++) Xftpd.8c: syslog(8) missing Xnfmail.8: delivermail(8) missing Xversatec.8: vpr(1) missing X.EF X.PP XThe amount of output produced by X.I cfman Xis startling. XA portion of the output of a sample run Xis seen in Figure 6. XSome of its complaints are relatively harmless, such as X.I dbm Xbeing in section X.B 3x Xrather than section X\fB3\fR, because the X.I man Xprogram can find entries with the subsection left off. XHaving inconsistent X.LB .TH Xheaders is also harmless, although the printed Xman pages will have headers that do not reflect their Xfilenames on the disk. XHowever, entries that refer to pages that are truly absent, like X.M exec 2 Xor X.M delivermail 8 , Xmerit closer attention. X.NH 2 XMultiple Architecture Support X.PP XAs mentioned in the discussion of the need for a \s-1MANPATH\s0, Xa site may for various reasons wish to maintain several Xcomplete sets of man pages on the same machine. Of course, Xa user could know to specify the full pathname of the Xalternate tree on the command line Xor set up their environment appropriately, but this is Xinconvenient. Instead, it is preferable Xto specify the machine type on the command line and let Xthe system worry about pathnames. X.ne 5 XConsider these examples: X.br X.CW X.nf X.na X.in +.5i Xman vax csh Xapropos sun rpc Xwhatis tahoe man X.in -.5i X.CE X.ad X.fi X.PP XTo implement this, Xwhen presented with more than one argument, X.I man X(in any of its three guises) Xchecks to see whether the first non-switch argument Xis a directory beneath X.I /usr/man . XIf so, it automatically adjusts its \s-1MANPATH\s0 to that subdirectory. X.PP XNot all vendors use precisely the same set of X.M man 7 Xmacros for formatting their man pages. Furthermore, it's Xhelpful to see in the header of the man page which manual Xit came from. The X.I man Xprogram therefore looks for a local X.I tmac.an Xfile in the root of the current man tree for alternate macro Xdefinitions. If this file exists, it will be used rather than Xthe system defaults for passing to X.I nroff Xor X.I troff Xwhen reformatting. X.NH XPerformance Analysis X.PP XThe X.I man Xprogram is one that is often used on the system, Xso users are sensitive to any significant degradation Xin response time. Because it is written in perl (an Xinterpreted language) this was cause for concern. XOn a \s-1CONVEX C2\s0, the C version runs faster when only Xone element is present in the \s-1MANPATH\s0. XHowever, when the \s-1MANPATH\s0 contains four Xelements, the C version bogs down considerably because of Xthe large number of X.M access 2 Xcalls it must make. X.PP XThe start-up time on the parsing Xof the script, now just over 1300 lines long, is around X0.6 seconds. This time can be reduced by dumping the Xparse tree that perl generates to disk and executing that instead. XThe expense of this action is disk space, as the current implementation Xrequires that the whole perl interpreter be included in the Xnew executable, not just the parse tree. This method Xyields performance superior to that of the C version, Xirrespective of the number of components in the user's \s-1MANPATH\s0, Xexcept occasionally on the initial run. This is because the Xprogram needs to be loaded Xinto memory the first time. If perl itself is installed ``sticky'' Xso it is memory resident, start-up time improves considerably. XIn any case, the Xtotal variance (on a \s-1CONVEX\s0) is Xless than two seconds in the worst case (and often Xunder one second), so it was deemed acceptable, particularly Xconsidering the additional functionality the perl version offers. X.PP XNothing in the algorithms employed in the X.I man Xprogram require that it be written in perl; Xit was just easier this way. It could be rewritten in C Xusing X.M dbm 3x Xroutines, although the development time would probably Xbe much longer. X.PP XThe X.I makewhatis Xprogram was originally a conglomeration of man calls to various individual Xutilities such as X\fIsed\fP, X\fIexpand\fP, X\fIsort\fP, and others. The perl rewrite runs in less than half the time Xof the original, and does a much better job. There are two Xreasons for the speed increase. The first is the cost of the numerous X.M exec 2 Xcalls made via the shell script used by the old version of X.I makewhatis . XThe second is that Xperl is optimized for text processing, which is most of what X.I makewhatis Xis doing. X.PP XTotal development time was only a few weeks, Xwhich was much shorter than originally anticipated. The short Xdevelopment cycle was chiefly attributable to Xthe ease of text processing in perl, the many built-in Xroutines for doing things that in C would have required Xextensive library development, and, last but not at all least, Xthe omission of the compilation stage in the normal edit-compile-test Xcycle of development when working with non-interpreted languages. X.NH XConclusions X.PP XThe system described above has been in operation for the last Xsix months on a large local network consisting of three dozen X\s-1CONVEX\s0 machines, a token \s-1VAX\s0, quite a few \s-1HP\s0 workstations Xand servers, and innumerable Sun workstations, all running different Xflavors of \s-1UNIX\s0. Despite this heterogeneity, Xthe same code runs on all systems without alterations. XFew problems have been seen, and those that did arise were quickly Xfixed in the scripts, which could be immediately redistributed Xto the network. The principal project goals of improved functionality, Xextensibility, and execution time were adequately met, and the Xexperience of rewriting a set of standard \s-1UNIX\s0 utilities Xin perl was an educational one. XMan pages stand a much better chance of being internally consistent Xwith each other. XResponse from the user and development community has Xbeen favorable. They have Xbeen relieved by the many bug fixes and pleasantly surprised Xby the new functionality. The suite of man programs will replace Xthe old man system in the next release of \s-1CONVEX\s0 utilities. X.\" Should be .BB here but that seems to mutilate my last BF figure X.sp 3 X.QP X.I X.SM XTom Christiansen left the University of Wisconsin with an \s-1MS-CS\s0 Xin 1987 Xwhere he had been a system administrator for 6 years to join X\s-1CONVEX\s0 XComputer Corporation in Richardson, Texas. XHe is a software development engineer Xin the Internal Tools Group there, designing software tools Xto streamline software development and systems administration Xand to improve overall system security. X.BE SHAR_EOF if test 34978 -ne "`wc -c < 'man.ms'`" then echo shar: "error transmitting 'man.ms'" '(should have been 34978 characters)' fi chmod 664 'man.ms' fi echo shar: "extracting 'COPYING'" '(151 characters)' if test -f 'COPYING' then echo shar: "will not over-write existing file 'COPYING'" else sed 's/^ X//' << \SHAR_EOF > 'COPYING' X# You are free to use, modify, and redistribute these scripts X# as you wish for non-commercial purposes provided that this X# notice remains intact. SHAR_EOF if test 151 -ne "`wc -c < 'COPYING'`" then echo shar: "error transmitting 'COPYING'" '(should have been 151 characters)' fi chmod 664 'COPYING' fi echo shar: "extracting 'man'" '(39119 characters)' if test -f 'man' then echo shar: "will not over-write existing file 'man'" else sed 's/^ X//' << \SHAR_EOF > 'man' X#!/usr/local/bin/perl X# X# man - perl rewrite of man system X# tom christiansen <tchrist@convex.com> X# X# Copyright 1990 Convex Computer Corporation. X# All rights reserved. X# X# -------------------------------------------------------------------------- X# begin configuration section X# X# this should be adequate for CONVEX systems. if you copy this script X# to non-CONVEX systems, or have a particularly outre local setup, you may X# wish to alter some of the defaults. X# -------------------------------------------------------------------------- X X$PAGER = $ENV{'PAGER'} || 'more'; X X# assume "less" pagers want -sf flags, all others must accept -s. X# note: some less's prefer -r to -f. you might also add -i if supported. X# X$is_less = $PAGER =~ /^\S*less(\s+-\S.*)?$/; X$PAGER .= $is_less ? ' -si' : ' -s'; # add -f if using "ul" X X# man roots to look in; you would really rather use a separate tree than X# manl and mann! see %SECTIONS and $MANALT if you do. X$MANPATH = &config_path; X X# default section precedence X$MANSECT = $ENV{'MANSECT'} || 'ln16823457po'; X X# colons optional unless you have multi-char section names X# note that HP systems want this: X# $MANSECT = $ENV{'MANSECT'} || '1:1m:6:8:2:3:4:5:7'; X X# alternate architecture man pages in
tchrist@convex.COM (Tom Christiansen) (01/08/91)
X# ${MANALT}/${machine}/man(.+)/*.\11* X$MANALT = $ENV{'MANALT'} || '/usr/local/man'; X X# default program for -t command X$TROFF = $ENV{'TROFF'} || 'nitroff'; X X$NROFF = 'nroff'; X$NROFF_CAN_BOLD = 0; # if nroff puts out bold as H\bH X X# this are used if filters are needed X$TBL = 'tbl'; X$NTBL = "$TBL -D"; # maybe you need -TX instead X$NEQN = 'neqn'; X$EQN = 'eqn'; X$SED = 'sed'; X X# define this if you don't have/want UL; X# without ul, you probably need COL defined unless your PAGER is very smart X# you also must use col instead of ul if you've any tbl'd man pages, such X# as from the X man pages or the eqnchar.7 page. X$COL = 'col'; X$UL = ''; # set to '' if you haven't got ul Xdie 'need either $UL or $COL' unless $UL || $COL; X X# need these for .Z files or dirs X$COMPRESS = 'compress'; X$ZCAT = 'zcat'; X$CAT = 'cat'; X X# define COMPRESS_DIR if pages might have moved to manX.Z/page.X (like HPs) X$COMPRESS_DIR = 1; X# define COMPRESS_PAGE if pages might have moved to manX/page.X.Z (better) X$COMPRESS_PAGE = 1; X X# Command to format man pages to be viewed on a tty or printed on a line printer X$CATSET = "$NROFF -h -man -"; X X$CATSET .= " | $COL" if $COL; X X# Command to typeset a man page X$TYPESET = "$TROFF -man"; X X X# flags: GNU likes -i, BSD doesn't; both like -h, but BSD doesn't document it X# if you don't put -i here, i'll make up for it later the hard way X$EGREP = '/usr/local/bin/egrep'; Xif (-x $EGREP) { X $EGREP .= ' -i -h'; X} else { X $EGREP = '/usr/bin/egrep'; X unless (-x $EGREP) { X $EGREP = ''; X } else { X $EGREP .= ' -h'; X } X} X X# sections that have verbose aliases X# if you change this, change the usage message X# X# if you put any of these in their own trees, comment them out and make X# a link in $MANALT so people can still say 'man local foo'; for local, X# cd $MANALT; ln -s . local X# for the other trees (new, old, public) put either them or links X# to them in $MANALT X# X%SECTIONS = ( X 'local', 'l', X 'new', 'n', X 'old', 'o', X 'public', 'p' ); X X# turn this on if you want linked (via ".so" or otherwise) man pages X# to be found even if the thing they are linked to doesn't know it's X# being linked to -- that is, its NAME section doesn't have reference X# to it. eg, if you call a man page 'gnugrep' but it's own NAME section X# just calls it grep, then you need this. usually a good idea. X# X$STUPID_SO = 1; X X# -------------------------------------------------------------------------- X# end configuration section X# -------------------------------------------------------------------------- X X# CONVEX RCS keeps CHeader; others may prefer Header X($bogus, $version) = split(/:\s*/,'$CHeader: man 0.40 91/01/07 15:40:15 $',2); Xchop($version); chop($version); X Xrequire 'getopts.pl'; X X# could do this via ioctl(0,$TIOCGETP,$sgtty) if I were really concerned X# X$rows = ($ENV{'TERMCAP'} =~ /:li#(\d+):/) ? $1 : 24; X X%options = ( X 'man', 'T:m:P:M:c:s:S:fkltvwdguhaiDK', X 'apropos', 'm:P:MvduaK', X 'whatis', 'm:P:M:vduh', X 'whereis', 'm:P:M:vduh' X); X X($program = $0) =~ s,.*/,,; X X$apropos = $program eq 'apropos'; X$whatis = $program eq 'whatis'; X$whereis = $program eq 'whman'; X$program = 'man' unless $program; X X&Getopts($options = $options{$program}) || &usage; X Xif ($opt_u) { X &version if $opt_v; X &usage; X # not reached X} X Xif ($opt_v) { X &version; X exit 0; X} X X&usage if $#ARGV < 0; X X$MANPATH = $opt_P if $opt_P; # backwards contemptibility X$MANPATH = $opt_M if $opt_M; X X$want_section = $opt_c if $opt_c; # backwards contemptibility X$want_section = $opt_s if $opt_s; X X$hard_way = $opt_h if $opt_h; X Xif ($opt_T) { X $opt_t = 1; X $TYPESET =~ s/$TROFF/$opt_T/; X $TROFF = $opt_T; X} X X$MANPATH = "$MANALT/$opt_m" # want different machine type (undoc) X if $machine = $opt_m; X X$MANSECT = $opt_S if $opt_S; # prefer our own section ordering X X$whatis = 1 if $opt_f; X$apropos = 1 if $opt_k || $opt_K; X$fromfile = 1 if $opt_l; X$whereis = 1 if $opt_w; X$grepman = 1 if $opt_g; X$| = $debug = 1 if $opt_d; X$full_index = 1 if $opt_i; X$show_all = 1 if $opt_a; X$stripBS = 1 if $opt_D; X X$roff = $opt_t ? 'troff' : 'nroff'; # for indirect function call X X X# maybe they said something like 'man vax ls' Xif ($#ARGV > 0) { X local($machdir) = $MANALT . '/' . $ARGV[0]; X if (-d $machdir) { X $MANPATH = $machdir; X $machine = shift; X } X} X X@MANPATH = split(/:/,$MANPATH); X X# assign priorities to the sections he cares about X# the nearer the front the higher the sorting priority X$secidx = 0; X$delim = ($MANSECT =~ /:/) ? ':' : ' *'; Xfor (reverse split(/$delim/, $MANSECT)) { X if ($_ eq '') { X warn "null section in $MANSECT\n"; X next; X } X $MANSECT{$_} = ++$secidx; X} X X Xif ($whatis) { X &whatis; X} elsif ($apropos) { X &apropos; X} elsif ($whereis) { X &whereis; X} elsif ($grepman) { X &grepman; X} else { X &man; X} X Xexit $status; X X# -------------------------------------------------------------------------- X# fill out @whatis array with all possible names of whatis files X# -------------------------------------------------------------------------- Xsub genwhatis { X local($elt,$whatis); X X for $elt (@MANPATH) { X $whatis = "$elt/whatis"; X push(@whatis, $whatis) if -f $whatis; X } X X die "$program: No whatis databases found, please run makewhatis\n" X if $#whatis < 0; X} X X# -------------------------------------------------------------------------- X# run whatis (man -f) X# -------------------------------------------------------------------------- Xsub whatis { X local($target, %seeking, $section, $desc, @entries); X X &genwhatis; X X for $target (@ARGV) { $seeking{$target} = 1; } X X if ($hard_way) { X &slow_whatis; X } else { X &fast_whatis; X } X X for $target (keys %seeking) { X print "$program: $target: not found.\n"; X $status = 1; X } X} X X# -------------------------------------------------------------------------- X# do whatis lookup against dbm file(s) X# -------------------------------------------------------------------------- Xsub fast_whatis { X local($entry, $cmd, $page, $section, $desc, @entries); X X for $INDEX (@whatis) { X unless (-f "$INDEX.pag" && dbmopen(INDEX,$INDEX,0444)) { X warn "$program: No dbm file for $INDEX: $!\n" if $debug; X #$status = 1; X if (-f $INDEX) { X local(@whatis) = ($INDEX); # dynamic scoping obfuscation X &slow_whatis; X } X next; X } X for $target (@ARGV) { X local($ext); X @entries = &quick_fetch($target,'INDEX'); X next if $#entries < 0; X # $target =~ s/([^\w])/\\$1/g; X for $entry (@entries) { X ($cmd, $page, $section, $desc) = split(/\001/, $entry); X # STUPID_SO is one that .so's that reference things that X # don't know they are being referenced. STUPID_SO may cause X # some peculiarities. X unless ($STUPID_SO) { X next unless $cmd =~ /$target/i || $cmd =~ /\.{3}/; X } X X delete $seeking{$target}; X ($ext) = $page =~ /\.([^.]*)$/; X printf("%-20s - %s\n", "$cmd ($ext)", $desc); X } X } X dbmclose(INDEX); X } X X} X X# -------------------------------------------------------------------------- X# do whatis lookup the hard way X# -------------------------------------------------------------------------- Xsub slow_whatis { X local($query); X local($WHATIS); X X for (@ARGV) { s/([^\w])/\\$1/g; } X X $query = '^[^-]*\b?(' . join('|',@ARGV) . ')\b[^-]* -'; X X if ($EGREP) { X if (&run("$EGREP '$query' @whatis")) { X # pity can't tell which i found X %seeking = (); X } X } else { X foreach $WHATIS (@whatis) { X unless (open WHATIS) { X warn "can't open $WHATIS: $!\n"; X next; X } X while (<WHATIS>) { X next unless /$query/i; X ($target = $+) =~ y/A-Z/a-z/; X delete $seeking{$target}; X print; X } X close WHATIS; X } X } X} X X# -------------------------------------------------------------------------- X# run apropos (man -k) X# -------------------------------------------------------------------------- Xsub apropos { X local($_, %seeking, $target, $query); X &genwhatis; X X # fold case on apropos args X for (@ARGV) { X y/A-Z/a-z/; X $seeking{$_} = 1; X s/(\W)/\\$1/g unless $opt_K; X } X $query = join('|',@ARGV); X X X if ($EGREP) { X # need to fake a -i flag? X unless ($EGREP =~ /-\w*i/) { X local($C); X local(@pat) = split(//,$query); X for (@pat) { X ($C = $_) =~ y/a-z/A-Z/ && ($_ = '[' . $C . $_ . ']'); X } X $query = join('',@pat); X } X if (&run("$EGREP '$query' @whatis | $PAGER")) { X %seeking = (); X } X } else { # use perl X foreach $WHATIS (@whatis) { X unless (open WHATIS) { X warn "can't open $WHATIS: $!\n"; X next; X } XWHATIS: while (<WHATIS>) { X next unless /$query/io; # /o ok, because only called once X $target = $+; X $target =~ s/\\//g; X delete $seeking{$query}; X print; X } X close WHATIS; X } X X } X X for $target (keys %seeking) { X warn "$program: $target: nothing appropriate\n"; X $status = 1; X } X} X X# -------------------------------------------------------------------------- X# print out usage message via pager and exit X# -------------------------------------------------------------------------- Xsub usage { X unless ($opt_u) { X warn "usage: $program [-flags] topic ...\n"; X warn " (use -u for long usage message)\n"; X } else { X open (PIPE, "| $PAGER"); X print PIPE <<USAGE; # in case he wants a page XUSAGE SUMMARY: X man [-flags] [section] page[/index] ... X (section is [1-8lnop], or "new", "local", "public", "old") X (index is section or subsection header) X X man [-flags] -f topic ... X (aka "whatis") X X man [-flags] -k keyword ... X (aka "apropos") X XFLAGS: (most only make sense when invoked as 'man') X -a show all possible man pages for this topic X -l file do man processing on local file X -f topic list table of contents entry for topic X -k keyword give table of contents entries containing keyword X -K pattern as -K but allow regexps X -g pattern grep through all man pages for patterns X -w topic which files would be shown for a given topic X -i topic show section and subsection index for use with topic/index X X -M path use colon-delimited man path for searching (also as -P) X -S sects define new section precedence X X -t troff the man page X -T path call alternate typesetter on the man page X X -d print out all system() commands before running them X -h do all lookups the hard way, ignoring any DBM files X -u this message X -v print version string X -D strip backspaces from output X XENVIRONMENT: X \$PAGER pager to pipe terminal-destined output through X \$MANPATH like -M path X \$MANSECT like -S sects X \$MANALT used for alternate hardware types (or obsolete -m flag) X \$TROFF like -T path X XCURRENT DEFAULTS: X PAGER $PAGER X MANPATH $MANPATH X MANSECT $MANSECT X MANALT $MANALT X TROFF $TROFF X XNOTES: (\$manroot is each component in \$MANPATH) X * If \$manroot/whatis DBM files do not exist, a warning will be X printed (if -d flag) and -h will be assumed for that \$manroot only. X * If \$manroot/tmac.an exists, it will be used for formatting X instead of the normal -man macros. X * Man pages may be compressed either in (for example) man1.Z/who.1 X or man1/who.1.Z; cat pages will go into corresponding places. X * If the man page contains .EQ or .TS directives, eqn and/or tbl X will be invoked as needed at format time. XUSAGE X close PIPE; X } X warn "couldn't run long usage message thru $PAGER?!?!\n" if $?; X exit 1; X} X X# -------------------------------------------------------------------------- X# lookup a given key in the given man root; returns list of hits X# -------------------------------------------------------------------------- Xsub fetch { X local($key,$root) = @_; X local(%recursed); X X return $dbmopened{$root} X ? &quick_fetch($key,$dbm{$root}) X : &slow_fetch($key,$root); X} X X# -------------------------------------------------------------------------- X# do a quick fetch of a key in the dbm file, recursing on indirect references X# -------------------------------------------------------------------------- Xsub quick_fetch { X local($key,$array) = @_; X local(@retlist) = (); X local(@tmplist) = (); X local($_, $entry); X local($name, $ext); X local(@newlist); X X return @retlist unless $entry = eval "\$$array".'{$key};'; X X if ($@) { chop $@; die "bad eval: $@"; } X X @tmplist = split(/\002/, $entry); X for (@tmplist) { X if (/\001/) { X push(@retlist, $_); X } else { X ($name, $ext) = /(.+)\.([^.]+)/; X push(@retlist, X grep(/[^\001]+\001[^\001]+\001${ext}\001/ || /[^\001]+${ext}\001/, X &quick_fetch($name, $array))) X unless $recursed{$name}++; X # explain and diction are near duplicate man pages referencing X # each other, requiring the $recursed check. one should be removed X } X } X return @retlist; X} X X# -------------------------------------------------------------------------- X# do a slow fetch for target using perl's globbing notation X# -------------------------------------------------------------------------- Xsub slow_fetch { X local($target,$root) = @_; X local($glob, $stem, $entry); X X if ($want_section) { X if ($MANSECT{$want_section}) { X $stem = $want_section; X } else { X $stem = substr($want_section,0,1); X } X $glob = "man$stem*"; X } else { X $glob = 'man*'; X } X $glob = "$root/$glob/$target.*"; X return <${glob}>; X} X X# -------------------------------------------------------------------------- X# run 'man -w' X# -------------------------------------------------------------------------- Xsub whereis { X local($target, @files); X X foreach $target (@ARGV) { X @files = &find_files($target); X if ($#files < $[) { X warn "$program: $target not found\n"; X $status = 1; X } else { X print "$target: "; X for (@files) { print " ", &verify($_); } X print "\n"; X } X } X} X X X# -------------------------------------------------------------------------- X# what are the file names matching this target? X# -------------------------------------------------------------------------- Xsub find_files { X local($target) = @_; X local($root, $entry); X local(@retlist) = (); X local(@tmplist) = (); X local(@entries) = (); X local($tar_regx); X local($found) = 0; X # globals: $vars, $called_before, %dbm, $hard_way (kinda) X X $vars = 'dbm00'; # var for magic autoincrementation X X ($tar_regx = $target) =~ s/(\W)/\\$1/g; # quote meta X X if (!$hard_way && !$called_before++) { X # generate dbm names X for $root (@MANPATH) { X $dbm{$root} = $vars++; # magic incr X $string = "dbmopen($dbm{$root},\"$root/whatis\",0444);"; X unless (-f "$root/whatis.pag" && eval $string) { X if ($@) { X chop $@; X warn "Can't eval $string: $@"; X } else { X warn "No dbm file for $root/whatis: $!\n" if $debug; X } X #$status = 1; X next; X } X $dbmopened{$root} = 1; X } X } X X for $root (@MANPATH) { X local($fullname); X @tmplist = (); X if ($hard_way || !$dbmopened{$root}) { X next unless -d $root; X warn "slow fetch on $target in $root\n" if $debug; X @tmplist = &slow_fetch($target,$root); X } else { X @entries = &fetch($target,$root); X next if $#entries < 0; X X for $entry (sort @entries) { X ($cmd, $page, $section, $desc) = split(/\001/, $entry); X X # STUPID_SO is so that .so's that reference things that X # don't know they are being referenced. STUPID_SO may X # cause peculiarities. X unless ($STUPID_SO) { X next unless $cmd =~ /$tar_regx/i || $cmd =~ /\.{3}/; X } X push(@tmplist, "$root/man$section/$page"); X } X } X push(@retlist, sort bysection @tmplist); X last if $#retlist >= 0 && $hard_way; X } X# unless (@retlist || $hard_way) { X# # shameless (ab?)use of dynamic scoping X# local($hard_way) = 1; X# warn "recursing on find_files\n" if $debug; X# return &find_files($target); X# } X return &trimdups(@retlist); X} X X# -------------------------------------------------------------------------- X# run a normal man command X# -------------------------------------------------------------------------- Xsub man { X local($target,$page); X $isatty = -t STDOUT; X X &get_section; X X while ($target = shift(@ARGV)) { X undef $idx_topic; X X if (!$fromfile && $target =~ m!^([^/]+)/(.*)!) { X if (!$isatty) { X warn "$program: no tty, so no pager to prime with index\n"; X $target = $1; X } else { X ($target, $idx_topic) = ($1, $2); X } X } else { X undef $idx_topic; X } X X if ($show_all) { X local(@pages); X local($was_defined) = defined $idx_topic; X @pages = &find_files($target); X if (!@pages) { X &no_entry($target); X next; X } X while ($tpage = shift @pages) { X undef $idx_topic unless $was_defined; X do $roff(&verify($tpage)); X &prompt_RTN("to read $pages[0]") X if $roff eq 'nroff' && @pages; X } X } else { X $target = &get_page($target) unless $fromfile; X do $roff($target) if $target; X } X &prompt_RTN("to read man page for $ARGV[0]") X if $roff eq 'nroff' && @ARGV; X } X} X X# -------------------------------------------------------------------------- X# find out if he wants a special section and save in $want_section X# -------------------------------------------------------------------------- Xsub get_section { X if (!$want_section) { X local($section) = $ARGV[0]; X $section =~ tr/A-Z/a-z/; X X if ($want_section = $SECTIONS{$section}) { X shift @ARGV; X } elsif (defined($MANSECT{$section}) || $section =~ /^\d\S*$/i) { X $want_section = shift @ARGV; X } X } X $want_section =~ tr/A-Z/a-z/; X X die "But what do you want from section $want_section?\n" X if $want_section && $#ARGV < 0; X} X X# -------------------------------------------------------------------------- X# pick the first page matching his target and search orders X# -------------------------------------------------------------------------- Xsub get_page { X local($target) = @_; X local(@found, @want); X X unless (@found = &find_files($target)) { X &no_entry($target); X return ''; X } X X if (!$want_section) { X @want = @found; X } else {{ X local($patsect); # in case it's section 3c++ X ($patsect = $want_section) =~ s/(\W)/\\$1/g; X X # try exact match first X last if @want = grep (/\.$patsect$/, @found); X X # otherwise how about a subsection X last if @want = grep (/\.$patsect[^.]*$/, @found); X X # maybe it's in the wrong place (mano is notorious for this) X last if @want = grep (/man$patsect[^.]*\//, @found); X X &no_entry($target); X return ''; X }} X X do { X ($found = &verify($want[0])) || shift @want; X } until $found || $#want < 0; X X return $found; X} X X# -------------------------------------------------------------------------- X# figure out full path name of man page, which may have been compressed X# -------------------------------------------------------------------------- Xsub verify { X local($path) = @_; X local($orig) = $path; X X return $path if -f $path; X X if ($COMPRESS_PAGE) { X $path .= '.Z'; X return $path if -f $path; X $path =~ s/.Z//; X } X X if ($COMPRESS_DIR) { X $path =~ s-(/[^/]*)$-.Z$1-; X return $path if -f $path; X } X X warn "$program: $orig has disappeared -- rerun makewhatis\n"; X $status = 1; X return ''; X} X X X# -------------------------------------------------------------------------- X# whine about something not being found X# -------------------------------------------------------------------------- Xsub no_entry { X print STDERR "No manual entry for $_[0]"; X if ($machine || $want_section) { X print STDERR " in"; X print STDERR " section $want_section of" if $want_section; X print STDERR " the"; X print STDERR " $machine" if $machine; X print STDERR " manual"; X } X print STDERR ".\n"; X $status = 1; X} X X# -------------------------------------------------------------------------- X# order by section. if the complete extension has a section X# priority, use that. otherwise use the first char of extension X# only. undefined priorities are lower than any defined one. X# -------------------------------------------------------------------------- Xsub bysection { X local ($e1, $e2, $p1, $p2, $s1, $s2); X X ($s1, $e1) = $a =~ m:.*/man([^/]+)/.*\.([^.]+)(\.Z)?$:; X ($s2, $e2) = $b =~ m:.*/man([^/]+)/.*\.([^.]+)(\.Z)?$:; X X $e1 = $s1 if $e1 !~ /^${s1}.*/; X $e2 = $s2 if $e2 !~ /^${s2}.*/; X X $p1 = $MANSECT{$e1} || $MANSECT{substr($e1,0,1)}; X X $p2 = $MANSECT{$e2} || $MANSECT{substr($e2,0,1)}; X X $p1 == $p2 ? $a cmp $b : $p2 <=> $p1; X} X X# -------------------------------------------------------------------------- X# see whether they want to start at a subsection, then run the command X# -------------------------------------------------------------------------- Xsub run_topic { X local($_); X local($menu_rtn) = defined $idx_topic && $idx_topic eq ''; X { X &append_sub_topic; X last if $idx_topic eq "\004"; X if ($idx_topic eq '0') { X $menu_rtn = 0; X $idx_topic = ''; X $command =~ s: '\+/[^']*'::; X } X $fromfile ? &reformat($command) : &run($command); X if ($menu_rtn) { X $idx_topic = ''; X &prompt_RTN("to return to the index"); X $command =~ s! '\+/.*$!!; X redo; X } X } X X} X X# -------------------------------------------------------------------------- X# run through the typesetter X# -------------------------------------------------------------------------- Xsub troff { X local ($file) = $_[0]; X local ($command); X local ($manroot); X local ($macros); X X ($manroot) = $file =~ m,^(.*)/man([^\.]*)(\.Z)?/([^/]*),; X X $command = ((($file =~ m:\.Z:) X ? $ZCAT X : $CAT) X . " < $file | $TYPESET"); X X $command =~ s,-man,$manroot/tmac.an, if -e "$manroot/tmac.an"; X X &insert_filters($command,$file); X &run($command); X} X X# -------------------------------------------------------------------------- X# just run a regular nroff, possibly showing the index first. X# -------------------------------------------------------------------------- Xsub nroff { X local($manpage) = $_[0]; X local($catpage); X local($tmppage); X local($command); X local(@saveidx); X local($manroot); X local($macros); X local($intmp); X local(@st_cat, @st_man); X X die "trying to nroff a null man page" if $manpage eq ''; X X umask 022; X X if ($full_index) { X &show_index($manpage); X return; X } X if ($fromfile) { X $command = (($manpage =~ m:\.Z/:) ? $ZCAT : $CAT) X . " < $manpage | $CATSET"; X &insert_filters($command, $manpage); X } else { X require 'stat.pl' unless defined &Stat; X # compiled version has this already X X X ($catpage = $manpage) X =~ s,^(.*)/man([^\.]*)(\.Z)?/([^/]*)$,$1/cat$2/$4,; X X $manroot = $1; X X # Does the cat page exist? X if (! -f $catpage && $COMPRESS_DIR){ X # No, maybe it is compressed? X if (-f "$1/cat$2.Z/$4"){ X # Yes it was. X $catpage = "$1/cat$2.Z/$4"; X } else { X # Nope, the cat file doesn't exist. X # Prefer the compressed cat directory if it exists. X $catpage = "$1/cat$2.Z/$4" X if $catpage !~ /\.Z$/ && -d "$1/cat$2.Z"; X } X } X X X @st_man = &Stat($manpage); X @st_cat = &Stat($catpage); X X if ($st_cat[$ST_MTIME] < $st_man[$ST_MTIME]) { X X $command = (($manpage =~ m:\.Z:) ? $ZCAT : $CAT) X . " < $manpage | $CATSET"; X X $command = &insert_filters($command, $manpage); X $command =~ s,-man,$manroot/tmac.an, if -e "$manroot/tmac.an"; X X ($catdir = $catpage) =~ s!^(.*/?cat[^/]+)/[^/]*!$1!; X X chdir $manroot; X X $tmppage = "$catpage.$$"; X X unless (-d $catdir && -w _ X && open(tmppage, ">$tmppage") # usually EROFS X && close(tmppage) ) X { X $catpage = $tmppage = "/tmp/man.$$"; X $intmp = 1; X } X X printf STDERR "Reformatting page. Please wait ... " if $isatty; X X $command .= "| $COMPRESS" if $catpage =~ /\.Z/; X $command .= "> $tmppage"; X X $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = $SIG{'TERM'} X = 'tmp_cleanup'; X XREFORMAT: { unless (&reformat($command)) { X warn "$program: nroff of $manpage into $tmppage failed\n"; X unlink $tmppage; X if (!$intmp++) { X $catpage = $tmppage = "/tmp/man.$$"; X warn "$program: hang on... retrying into $tmppage\n"; X $command =~ s/> \S+$/> $tmppage/; X $status = 0; X redo REFORMAT; X } else { X #$status = 1; X return; X } X }} X warn "done\n" if $isatty; X X $intmp || rename($tmppage,$catpage) || X die "couldn't rename $tmppage to $catpage: $!\n"; X X $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = $SIG{'TERM'} X = 'DEFAULT'; X X } X $command = (($catpage =~ m:\.Z:) X ? $ZCAT X : $CAT) X . " < $catpage"; X } X if (-z $catpage) { X unlink $catpage; X die "$program: $catpage was length 0; disk full?\n"; X } X $command .= "| $UL" if $UL; X $command .= "| $SED 's/.\b//g'" if $stripBS; X $command .= "| $PAGER" if $isatty; X X &run_topic; X unlink($tmppage) if $intmp; X} X X X# -------------------------------------------------------------------------- X# modify $command to prime the pager with the subsection they want X# -------------------------------------------------------------------------- Xsub append_sub_topic { X if (defined $idx_topic) {{ X local($key); X last if $idx_topic eq '0'; X unless ($idx_topic) { X $idx_topic = &pick_index; X last if $idx_topic eq "\004" || $idx_topic eq '0'; X } X if ($idx_topic =~ m!^/!) { X $command .= " '+$idx_topic'"; X last; X } X unless ($key = &find_index($manpage, $idx_topic)) { X warn "No subsection $idx_topic for $manpage\n\n"; X $idx_topic = ''; X redo; X } X $key =~ s/([!-~])/$1.$1/g unless $is_less; X $command .= " '+/^[ \t]*$key'"; X }} X} X X X# -------------------------------------------------------------------------- X# present subsections and let user select one X# -------------------------------------------------------------------------- Xsub pick_index { X local($_); X print "Valid sections for $page follow. Choose the section\n"; X print "index number or string pattern. (0 for full page, RTN to quit.)\n\n"; X &show_index; X print "\nWhich section would you like? "; X ($_ = <>) ? chop : ($_ = "\004"); X $_ = "\004" if 'quit' =~ /^$_/; X return $_; X} X X# -------------------------------------------------------------------------- X# strip arg of extraneous cats and redirects X# -------------------------------------------------------------------------- Xsub unshell { X $_[0] =~ s/^\s*cat\s*<?\s*([^\s|]+)\s*\|\s*([^|]+)/$2 < $1/; X $_[0] =~ s/^([^|<]+)<([^Z|<]+)$/$1 $2/; X ($roff eq 'troff') && $_[0] =~ s#(/usr/man/pr\S+)\s+(\S+)#$2 $1#; X} X X# -------------------------------------------------------------------------- X# call system on command arg, stripping of sh-isms and echoing for debugging X# -------------------------------------------------------------------------- Xsub run { X local($command) = $_[0]; X X &unshell($command); X X warn "running: $command\n" if $debug; X if (system $command) { X $status = 1; X printf STDERR "\"%s\" exited %d, sig %d\n", $command, X ($? >> 8), ($? & 255) if $debug; X } X return ($? == 0); X} X X# -------------------------------------------------------------------------- X# check if page needs tbl or eqn, modifying command if needed X# add known problems for PR directory if applicable X# -------------------------------------------------------------------------- Xsub insert_filters { X local($filters,$eqn, $tbl, $_); X local(*PAGE); X local($c, $PAGE) = @_; X local($page,$sect, $prs); X
tchrist@convex.COM (Tom Christiansen) (01/08/91)
X ( $page = $PAGE ) =~ s/\.Z//; X $page =~ s#.*/([^/]+)$#$1#; X X $PAGE = "$ZCAT < $PAGE|" if $PAGE =~ /\.Z/; X X (open PAGE) || die ("$program: can't open $PAGE to check filters: $!\n"); X warn "open $PAGE to check for filters in $_[0]\n" if $debug; X X while (<PAGE>) { X if (/^\.EQ/) { X $_ = <PAGE>; X $eqn = 1 unless /\.(if|nr)/; # has eqn output not input X } X if (/^\.TS/) { X $_ = <PAGE>; X $tbl = 1 unless /\.(if|nr)/; # has tbl output not input X } X last if $eqn && $tbl; X } X close PAGE; X X if ($roff eq 'troff') { X $eqn && $_[0] =~ s/(\S+roff)/$EQN | $1/; X $tbl && $_[0] =~ s/(\S+roff)/$TBL | $1/; X } else { # nroff X $eqn && $_[0] =~ s/(\S+roff)/$NEQN | $1/; X $tbl && $_[0] =~ s/(\S+roff)/$NTBL | $1/; X } X X ($sect) = $page =~ /\.(\d)[^.]*$/; X $prs = "/usr/man/pr$sect/$page"; X if (-e $prs) { X warn "found PRs for $page\n" if $debug; X if ($roff eq 'nroff') { X $_[0] =~ s/ - / - $prs/; X } else { X $_[0] .= " $prs"; X } X } else { X print "no PRS for $page in $prs\n" if $debug; X } X $_[0]; X} X X# -------------------------------------------------------------------------- X# due to aliasing the dbase sometimes has the same thing twice X# -------------------------------------------------------------------------- Xsub trimdups { X local(%seen) = (); X local(@retlist) = (); X X while ($file = shift) { X push(@retlist,$file) unless $seen{$file}++; X } X return @retlist; X} X X# -------------------------------------------------------------------------- X# just print the version X# -------------------------------------------------------------------------- Xsub version { X warn "$program: version is \"$version\"\n" ; X} X X# -------------------------------------------------------------------------- X# create and display subsection index via pager X# -------------------------------------------------------------------------- Xsub show_index { X local($_); X &load_index($_[0]); X if ($#ssindex > ($rows - 4) && $isatty) { X print "Hit <RTN> for $#ssindex subsections via pager: "; X $_ = <STDIN>; X if ($no_idx_file) { X open (PAGER, "| $PAGER"); X print PAGER @ssindex; X close PAGER; X } else { X &run("$PAGER $idx_file"); X } X } else { X print STDOUT @ssindex; X } X} X X# -------------------------------------------------------------------------- X# find closest match on index selection in full index X# -------------------------------------------------------------------------- Xsub find_index { X local($manpage, $expr) = @_; X local($_, @matches); X X &load_index($manpage); X X $expr =~ s!^/+!!; X X for (@ssindex) { X s/^\s*\d+\s+//; X s/\s+\d+\s*$//; X } X X if ($expr > 0) { X return $ssindex[$expr]; X } else { X $ssindex[0] = ''; X if (@matches = grep (/^$expr/i, @ssindex)) { X return $matches[0]; X } elsif (@matches = grep (/$expr/i, @ssindex)) { X return $matches[0]; X } else { X return ''; X } X } X} X X# -------------------------------------------------------------------------- X# read in subsection index into @ssindex X# -------------------------------------------------------------------------- Xsub load_index { X local($manpage) = @_; X $no_idx_file = 0; X &getidx($manpage) if $#saveidx < 0; X @ssindex = @saveidx; X die "should have have an index for $manpage" if $#saveidx < 0; X} X X# -------------------------------------------------------------------------- X# create subsection index is out of date wrt source man page X# -------------------------------------------------------------------------- Xsub getidx { X local($manpage) = @_; X local($is_mh); X local($_, $i, %lines, %sec, $sname, @snames); X local(@retlist, $maxlen, $header, @idx , @st_man, @st_idx); X # global no_idx_file, idx_file X X ( $idx_file = $manpage ) =~ s:/man(\w+)(\.Z)?/:/idx$1/:; X $idx_file =~ s/\.Z//; X X require 'stat.pl' unless defined &Stat; X X @st_man = &Stat($manpage); X @st_idx = &Stat($idx_file); X X if ($st_man[$ST_MTIME] < $st_idx[$ST_MTIME]) { X unless (open idx_file) { X warn "$program: can't open $idx_file: $!\n"; X return (); X } X @retlist = <idx_file>; X close idx_file; X return @saveidx = @retlist; X } X X if (!open(manpage, $manpage =~ /\.Z/ ? "$ZCAT < $manpage|" : $manpage)) { X warn "$program: can't open $manpage: $!\n"; X return (); X } X warn "building section index\n" if $debug; X ($header = "Subsections in $manpage") =~ s!/?\S*/!!; X $maxlen = length($header); X push(@snames, $sname = 'preamble');; X X # MH has these alias for sections and subsectdions X if ($is_mh = $manpage =~ m:/mh/:) { X %mh_sections = ( X "NA", "NAME", X "SY", "SYNOPSIS", X "DE", "DESCRIPTION", X "Fi", "FILES", X "Pr", "PROFILE", X "Sa", "SEE ALSO", X "De", "DEFAULTS", X "Co", "CONTEXT", X "Hh", "HELPFUL HINTS", X "Hi", "HISTORY", X "Bu", "BUGS" X ); X $mh_expr = join('|',keys %mh_sections); X } X X while (<manpage>) { X if ($is_mh && /^\.($mh_expr)/) { X $sname = $mh_sections{$+}; X $maxlen = length($sname) if $maxlen < length($sname); X push(@snames,$sname); X } X if (/^\.s[sh]\s+(.*)/i ) { X $line = $_; X $_ = $1; X s/"//g; X s/\\f([PBIR]|\(..)//g; # kill font changes X s/\\s[+-]?\d+//g; # kill point changes X s/\\&//g; # and \& X s/\\\((ru|ul)/_/g; # xlate to '_' X s/\\\((mi|hy|em)/-/g; # xlate to '-' X s/\\\*\(..//g; # no troff strings X s/\\//g; # kill all remaining backslashes X $sname = $_; X $_ = $line; X $maxlen = length($sname) if $maxlen < length($sname); X push(@snames,$sname); X } X $lines{$sname}++; X } X X $mask = sprintf("%%2d %%-%ds %%5d\n", $maxlen + 2); X X $no_idx_file = $idx_file eq $manpage || !open(idx, ">$idx_file"); X X $line = sprintf(sprintf("Idx %%-%ds Lines\n", $maxlen + 2), $header); X @retlist = ($line); X X for ($i = 1; $i <= $#snames; $i++) { X push(@retlist, sprintf($mask, $i, $snames[$i], $lines{$snames[$i]})); X } X if (!$no_idx_file) { X warn "storing section index in $idx_file\n" if $debug; X print idx @retlist; X close idx; X } X return @saveidx = @retlist; X} X X# -------------------------------------------------------------------------- X# interrupted -- unlink temp page X# -------------------------------------------------------------------------- Xsub tmp_cleanup { X warn "unlink $tmppage\n" if $debug; X unlink $tmppage; X exit -1; X} X X X# -------------------------------------------------------------------------- X# print line with C\bC style emboldening X# -------------------------------------------------------------------------- Xsub print { X local($_) = @_; X X if (!$inbold) { X print; X } else { X local($last); X for (split(//)) { X if ($last eq "\033") { X print; X } else { X print /[!-~]/ ? $_."\b".$_ : $_; X } X $last = $_; X } X } X} X X# -------------------------------------------------------------------------- X# reformat the page with nroff, fixing up bold escapes X# -------------------------------------------------------------------------- Xsub reformat { X local($_) = @_; X local($nroff, $col); X local($inbold) = 0; X X if ($NROFF_CAN_BOLD) { X return &run($_); X } X X &unshell($_); X ($nroff, $col) = m!(.*)\|\s*($COL.*)!; X X warn "$nroff | (this proc) | $col\n" if $debug; X X open (NROFF, "$nroff |"); X $colpid = open (COL, "| $col"); X X select(COL); X X while (<NROFF>) { X s/\033\+/\001/; X s/\033\,/\002/; X if ( /^([^\001]*)\002/ || /^([^\002]*)\001/ ) { X &print($1); X $inbold = !$inbold; X $_ = $'; X redo; X } X &print($_); X } X X close NROFF; X if ($?) { X warn "$program: \"$nroff\" failed!\n" if $debug; X $status++; X } X close COL; X if ($?) { X warn "$program: \"$col\" failed!\n" if $debug; X $status++; X } X select(STDOUT); X $status == 0; X} X X# -------------------------------------------------------------------------- X# prompt for <RET> if we're a tty and have a non-stopping pager X# -------------------------------------------------------------------------- Xsub prompt_RTN { X local($why) = $_[0] || "to continue"; X return unless $isatty; X unless ($is_less && $ENV{'LESS'} !~ /E/) { X print "Hit <RTN> $why: "; X $_ = <STDIN>; X } X} X X# -------------------------------------------------------------------------- X# dynamically determine MANPATH (if unset) according to PATH X# -------------------------------------------------------------------------- Xsub config_path { X local($_); # for traversing $PATH X local(%seen); # weed out duplicates X local(*manpath); # eventual return values X X if (defined $ENV{'MANPATH'}) { X $manpath = $ENV{'MANPATH'}; X } else { X for (split(/:/, $ENV{'PATH'})) { X next if $_ eq '.'; X next if $_ eq '..'; X s![^/+]*$!man! && -d && !$seen{$_}++ && push(@manpath,$_); X } X $manpath = join(':', @manpath); X } X # $manpath; # last expr is assign to this anyway X} X X# -------------------------------------------------------------------------- X# grep through MANPATH for a pattern X# -------------------------------------------------------------------------- Xsub grepman { X local($code, $_, $dir, $root, $FILE, $found); X X $code = "while (<FILE>) {\n"; X X for (@ARGV) { X s#/#\\/#g; X $code .= <<EOCODE; X if (/$_/) { X print "\$path: \$_"; X \$found++; X next; X } XEOCODE X } X X $code .= "}\n"; X X print "grep eval code: $code" if $debug; X X X foreach $root ( split(/:/, $MANPATH)) { X unless (chdir($root)) { X warn "can't chdir to $root: $!"; X $status++; X next; X } X foreach $dir ( <man?*> ) { X unless (chdir($dir)) { X warn "can't chdir to $root/$dir: $!"; X $status++; X next; X } X unless (opendir(DIR, '.')) { X warn "can't opendir $root/$dir: $!"; X $status++; X next; X } X foreach $FILE ( readdir(DIR) ) { X next if $FILE eq '.' || $FILE eq '..'; X $path = "$root/$dir/$FILE"; X if ($FILE !~ /\S\.\S/ || !-f $FILE) { X print "skipping non-man file: $path\n" if $debug; X next; X } X if ($FILE =~ /\.Z$/ || $dir =~ /\.Z$/) { X $FILE = "$ZCAT $FILE|"; X } X print STDERR "grepping $path\n" if $debug; X unless (open FILE) { X warn "can't open $root/$dir/$FILE: $!"; X $status++; X next; X } X eval $code; X die $@ if $@; X } X unless (chdir ($root)) { X warn "can't return to $root: $!"; X $status++; X last; X } X } X } X exit ($status || !$found); X} SHAR_EOF if test 39119 -ne "`wc -c < 'man'`" then echo shar: "error transmitting 'man'" '(should have been 39119 characters)' fi chmod 755 'man' fi echo shar: "extracting 'catwhatis'" '(553 characters)' if test -f 'catwhatis' then echo shar: "will not over-write existing file 'catwhatis'" else sed 's/^ X//' << \SHAR_EOF > 'catwhatis' X#!/usr/local/bin/perl X X$manpath = shift || $ENV{'MANPATH'} || '/usr/man/'; X Xfor $manroot (split(/:/, $manpath)) { X X unless (dbmopen(manroot, "$manroot/whatis", undef)) { X warn("Can't dbmopen $manroot/whatis: $!\n"); X next; X } X X print "$manroot:\n"; X X while (($key,$value) = each %manroot) { X X for (split(/\002/, $value)) { X if (/\001/) { X ($cmd, $page, $section, $desc) = split(/\001/); X printf("%-30s - %s\n", "$cmd ($section)", $desc); X } else { X printf("%-30s > %s\n", "$key", $_); X } X } X } X X dbmclose(manroot); X} SHAR_EOF if test 553 -ne "`wc -c < 'catwhatis'`" then echo shar: "error transmitting 'catwhatis'" '(should have been 553 characters)' fi chmod 775 'catwhatis' fi echo shar: "extracting 'countman'" '(326 characters)' if test -f 'countman' then echo shar: "will not over-write existing file 'countman'" else sed 's/^ X//' << \SHAR_EOF > 'countman' X#!/usr/local/bin/perl X# X# countman -- count the number of entries in a man tree X X$| = 1; Xfor (split(/:/, shift || $ENV{'MANPATH'} || '/usr/man')) { X dbmopen(%whatis, "$_/whatis", undef) || (warn "$0: dbmopen $_: $!\n",next); X print $_, ': '; X for ($count = 0; each %whatis; $count++) { } X print $count, "\n"; X} SHAR_EOF if test 326 -ne "`wc -c < 'countman'`" then echo shar: "error transmitting 'countman'" '(should have been 326 characters)' fi chmod 775 'countman' fi echo shar: "extracting 'cfman.8'" '(6282 characters)' if test -f 'cfman.8' then echo shar: "will not over-write existing file 'cfman.8'" else sed 's/^ X//' << \SHAR_EOF > 'cfman.8' X.TH CFMAN 8L \fIConvex-Internal\fP "15 November 1989" "\fBDocumentation Administration Toolkit\fP" X.de Sh X.br X.PP X.ne 4 X.ti -.5i X\fB\\$1\fR X.PP X.. X.de LB \" little and bold X.ft B X.if !"\\$1"" \&\s-1\\$1\s+1 \\$2 \\$3 \\$4 \\$5 \\$6 X.ft R X.. X.de Sp X.if t .sp .5v X.if n .sp X.. X.ds lq \&"\" X.ds rq \&"\" X.if t \ X. ds lq `` X.if t \ X. ds rq '' X.de Q X\*(lq\\$1\*(rq\\$2 X.. X.Sh NAME Xcfman \- cross-reference man pages for internal consistency X.Sh SYNOPSIS X.B cfman X[ X.B \-d Xlevel X] X[ X.B \-s Xsections X] X[ X.B \-p Xmanpath X] X[ X.B \-x Xxrefpath X] X[ pattern | pathname ] ... X.br X.Sh DESCRIPTION X.I XCfman Xis a X.I perl Xprogram that checks that man page sources Xare mutually consistent in their X.LB "SEE ALSO" Xreferences. XIt will also report any X.LB ".TH" Xline that claims the Xman page is in a different place than X.I cfman Xfound it. X.PP XWhen supplied with no arguments, X.I cfman Xwill check all files (matching *.*) it finds in each man directory in Xyour colon-delimited X.LB "$MANPATH" Xenvariable if set, or in X.I /usr/man Xotherwise. It first verifies that the X.LB ".TH" Xsays Xthe man page is really where it should be, e.g. if the Xline is X.br X.in +.5i X.nf X\f(TA X\&.TH\ \ WIDGET\ \ 8 X.in -.5i X\fP X.fi X.br Xthen \fIwidget.8\fP should be the filename currently Xbeing examined. All upper-case will map to all lower-case, Xbut mixed case will be preserved for compatibility with Xthe X.LB X11 Xman pages. X.PP X.I Cfman Xthen skips ahead to the X.LB "SEE ALSO" Xsection and retrieves Xall comma-delimited entries of the general Xform \fIpagename(section)\fP. It first looks in the file X\&../man\fIsection/pagename.section\fP. If this fails Xand the current file ended in one of \fB[npl]\fP, but the X.I section Xreferenced is either X\fB1\fP or \fB8\fP, then it will check in X.I ../man8. XFailing this, X.I cfman Xchecks to see whether the referenced man page has been Xinstalled stripped of its subsection, e.g. \fIuucp\fP(1c) Xhas found its way into \fIuucp\fP(1). It then checks Xto see whether something in section \fB1\fP has been mis-installed Xin section \fB8\fP, or vice versa, or either one in section \fBl\fP Xmis-installed in the Xin section \fB8\fP and vice-versa. If all else fails, X.I cfman Xwill guess that a man page is referenced without its Xproper subsection, as in a reference to \fIrcp(1)\fP Xthat should really have been to \fIrcp(1c)\fP. If it finds Xthe misplaced man page, it reports where the reference Xthought it was and where it really was. Otherwise it Xreports the man page as missing. X.PP XThe X.LB $MANPATH Xvariable may be overridden by Xthe \fB-p\fP option. XAll checks will Xbe performed across each subtree specified in the manpath X(either from the environment of the command line), Xunless altered with the \fB-x\fP option. As a short-cut, Xthe \fIxrefpath\fP may have a leading colon to indicate Xthat it is to be concatenation of the \fImanpath\fP Xand the supplied \fIxrefpath\fP. X.PP XYou can restrict the sections checked with the \fB-s\fP Xswitch. By default, sections 1 through 8 will be examined. XThe section may be a shell metacharacter expression, Xlike X.Q ? Xor X.Q [18lpn] . X.PP XYou may restrict the individual man pages cross-referenced Xby specifying which ones you're interested in on the command Xline. These may be full pathnames, simple names like X.Q tty , Xor a shell metacharacter expression like X.Q *net . XIf Xno period occurs in the simple name, it is assumed to mean that Xthe name may have any extension. If you list specific Xman pages on the command line and X.I cfman Xfinds none matching your specification, it will report this fact. XSee the X.LB "EXAMPLES" Xsection. X.PP XMan pages that are linked by placing a \fB.so\fP directive Xon the first line will be correctly followed, and no man page Xin the same subtree. Very limited support for alternate Xman macros is provided: the X.I "\fIRand MH Message Handling System\fP" 's Xman macro set are recognized, as is Larry Wall's X.LB .Sh Xreplacement for X.LB .SH. X.Sh DIAGNOSTICS XRequires X.I perl Xto be at least version 3.0, patchlevel 1 to run. The Xprogram will abort if you try to run it with an Xearlier version of perl X.PP XFive different tracing levels can be specified with the \fB-d\fP Xoption. If any debugging is turned on, the walk through Xthe different components of the manpath are traced. XDebug values are numeric and additive, and are interpreted Xthis way: X.Sp X.in +.5i X.nf X.ne 5 X 1 Trace each man page examined X 2 Trace each cross reference examined X 4 Trace each \s-1\fB.TH\s+1\fP check X 8 Trace each file-existence test X 16 Trace each line X'in -.5i X.fi X.Sp XTracing information and other warnings are printed to X\fIstderr\fP, but normal messages about bad cross references Xare printed to \fIstdout\fP as that is \fIcfman\fP's principle Xtask. X.PP XEmbedded X.I troff Xstring macros starting \e*( cannot be resolved, and they Xwill trigger a warning message if found in the X.LB .TH Xor X.LB "SEE ALSO" Xsections. X.Sh EXAMPLES X.nf X\f(TA Xcfman # do all in $MANPATH Xcfman -p /export/exec/sun3/share/man # sun man pages Xcfman -p $HOME/man:/usr/local/mh/man:/usr/local/man:/usr/man Xcfman -p /usr/local/man -x :/usr/man # xref also in /usr/man Xcfman -s 18nlp # only these sections Xcfman '*tty*' fubar # check for *tty*.* and fubar.* Xcfman `pwd`/*.[1-8] # just check these files Xcfman -s 23 'sys*' # sys*.* files in sections 2,3 Xcfman -s 1 -p /export/exec/sun3/share/man X.fi X\fR X.PP XThe last command produced this output on my machine: X.nf X\f(TA Xbanner.1v: thinks it's in banner(1) Xfoption.1: skyversion(8) missing Xfrom.1: prmail(1) missing Xmake.1: rstat(8c) missing Xman.1: apropos(1) missing Xold-perfmon.1: missing .TH Xoldperfmon.1: missing .TH Xoldsetkeys.1: thinks it's in setkeys(1) Xorganizer.1: restore(1v) really in restore(8) Xsunview.1: traffic(1) really in traffic(1c) Xsort.1v: thinks it's in sort(1) Xsum.1v: thinks it's in sum(1) X.fi X\fR X.Sh ENVIRONMENT XThe default manpath will be taken from X.LB $MANPATH Xif set. X.Sh "SEE ALSO" Xman(1), troff(1), perl(1), man(7). X.Sh BUGS XDue to the current implentation of globbing in X.I perl, Xyou can get X.Q "Arguments too long" Xerrors. The workaround is to run X.I cfman Xfirst on X.Q [a-m]* , Xand then on X.Q [n-z]* . X.Sh AUTHOR XTom Christiansen, \s-1CONVEX\s+1 Computer Corporation. SHAR_EOF if test 6282 -ne "`wc -c < 'cfman.8'`" then echo shar: "error transmitting 'cfman.8'" '(should have been 6282 characters)' fi chmod 644 'cfman.8' fi echo shar: "extracting 'FEATURES'" '(2356 characters)' if test -f 'FEATURES' then echo shar: "will not over-write existing file 'FEATURES'" else sed 's/^ X//' << \SHAR_EOF > 'FEATURES' XFeatures include but are not limited to: X X * almost always faster than standard man (try 'man me') X X * take much less diskspace for catpages X X * supports per-tree tmac macros X X * compressed man and cat files X X * user-definable man path via $MANPATH or -M optoin X X * $MANPATH autoconfigged based on $PATH if not set X X * user-definable section search order via -S or $MANSECT. Thus X programmers can get stty(3) before stty(1). X X * $PAGER support X X * show all the places you would find a man page (-w option) X and in what order. X X * display all available man page on a topic (-a option) X X * no limits on what subsections go where (if you want to add 7x, ok) X X * support for multi-char sections like man1m/*.1m or manavs/*.avs X X * man -K for regexp apropos X X * grep through all the man pages in $MANPATH X X * section and subsection indexing for long man pages X X * support for alternate architectures docs on same machine X X * ability to run man on a local file X X * ability to easily troff (or preview) a man page X X * recognizes embedded filter directives for tbl and eqn X X * does the right thing for man tree that don't have DBM whatis files X X * support for connecting online problem reports to right man page X X * there's an extended usage message (man -U) for further help X and to show current defaults. X X XHere are some features of this version of makewhatis: X X * it's faster. X X * tries hard to make pretty output, stripping troff directives. X X * doesn't blow up on more files in a man directory X than the shell will glob. X X * accepts troff string macros for the dashes in the X the NAME section. X X * prints a diagnostic for a malformed NAME section. X X * detects linked (hard, soft, or via .so) man pages X X * finds *all* references in the NAME section. X X * recognizes MH's man macros (and .Sh from lwall). X X * many other things that makewhatis used to do wrong X XHere are some supporting utilities that are included: X X * catman -- new version that groks compressed files X X * catwhatis -- display the whatis databases X X * straycats -- find cat pages with no man page ancestor X X * countman -- find how many man pages you can get at X X * cfman -- find bad SEE ALSO references in man pages SHAR_EOF if test 2356 -ne "`wc -c < 'FEATURES'`" then echo shar: "error transmitting 'FEATURES'" '(should have been 2356 characters)' fi chmod 664 'FEATURES' fi chmod 775 . echo shar: "done with directory 'man'" cd .. exit 0 # End of shell archive
tchrist@convex.COM (Tom Christiansen) (01/10/91)
Looks like the cfman program (the SEE ALSO chaser) I jammed into the
package was from my work tree instead of my release tree. Here's the
necessary patch to back off those minor changes. Apply to cfman:
16c16
< &Getopts('fd:s:p:P:x:') || &usage;
---
> &Getopts('d:s:p:P:x:') || &usage;
28d27
< $use_DBM = $opt_f;
335,337d333
< if ($use_DBM && &dbmopen($tree)) {
< next;
< }
403,411d398
< }
<
< sub dbmopen {
< local($tree) = $_[0];
< # globals: %dbmopened, %warned
< return 1 if $dbmopened{$tree};
<
< unless (-f "
<