brad@looking.ON.CA (Brad Templeton) (12/20/89)
Posting-number: Volume 9, Issue 84 Submitted-by: brad@looking.ON.CA (Brad Templeton) Archive-name: newsclip/part15 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 15 (of 15)." # Contents: doc/man.mm.2 # Wrapped by allbery@uunet on Tue Dec 19 20:10:09 1989 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'doc/man.mm.2' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'doc/man.mm.2'\" else echo shar: Extracting \"'doc/man.mm.2'\" \(53631 characters\) sed "s/^X//" >'doc/man.mm.2' <<'END_OF_FILE' XIf you know that ``foo'' is also fed by ``bar'' and ``baz'', you could Xcreate a string array with those three names, or just say: X.Bb Xreject if "foo" in path || "bar" in path || "baz" in path; X.Be X.H 2 "Regular Expressions" X.P XAs noted above, the search strings used with the \fBhas\fP operator Xare patterns from a language called ``regular expressions.'' The Xbasics of this system are defined in the documentation for the X\fBed\fP text editor, and the \fBgrep\fP and particularly \fBegrep\fP Xfile searching programs. X.P XPatterns in NewsClip follow the patterns of \fBegrep\fP, which is to Xsay they include the patterns of \fBed\fP, plus the following rules: X.AL X X.LI XThe special tag characters \\( and \\) are not supported. X.LI XA regular expression followed by a plus (\fB+\fP) matches one or more Xof the regular expression. (This is like \fB*\fP, except it matches 1 Xor more cases rather than 0 or more cases.) Thus ``a+'' matches X\fBa\fP, \fBaa\fP, \fBaaa\fP and so on. X.LI XA regular expression followed by a question mark (\fB?\fP) matches 0 Xor 1 occurrences of the regular expression, ie. the expression Xis optional. X.LI XYou can search for two different regular expressions by putting an or Xbar (\fB|\fP) between them. Either the first or the second may match. XBe warned that currently this is not very efficient. X.LI XYou can group patterns together with parentheses so that the scope of Xthe above rules applies where you like. ``abc(def|ghi)jkl'' matches X``abcdefjkl'' or ``abcghijkl,'' for example. X.LE X X.H 3 "Literal Patterns" X.P XClearly there are times when you don't want to use the regular expression Xmetacharacters in a search string -- you wish to search for the exact Xstring, even if it has the special characters in it. X.P XWhen you have a typed-in pattern string, you can do this easily by escaping Xthe special characters with a backslash. For example, \fB\\$\fP matches a Xreal dollar sign. X.P XIf you get a string from an article and you wish to use it as a pattern, Xyou should pass it through the \fBliteral\_pattern\fP function. This Xescapes all special characters for you, so that pattern matching on the Xresulting string will only match strings that contain the exact text of Xyour pattern string. For example, \fBliteral\_pattern( "It costs $5.93" )\fP Xwill be the string ``\fBIt costs \\$5\\.93\fP'' with the special characters Xescaped. X.P XIt is important to do this if you are storing strings from an article X(like the \fBsubject\fP) in a database for later searches using a database Xof patterns. X X X X.H 1 "Databases" X.P XQuite often your news clipping decisions will be based on past history -- Xlists of users, topics or sites that you either like or don't like. XOne particularly common desire is to ask to see or not see the followups Xto a given article. X.P XTo keep track of old information, and in general to read information to Xand from disk, NewsClip programs use the database type. X.P XA database is effectively an array of integer values that is indexed with X\fIstrings\fP instead of numbers. If you index into a database with a string, Xyou'll get back the integer value for that index, or 0 if the string isn't Xin the database at all. X.P XQuite often the integer value is unimportant, Xand all you care about is whether or not a given string has an entry in the Xdatabase or not. In this case, one can use the \fBin\fP operator to find Xout if a string, or any member from an array of strings, is ``in'' the Xdatabase. X.P XLet's say you have a list of USENET posters whom you don't like. You might Xwant to give negative scores to all the articles they write. X.Bb X extern userid from; X extern int score; X database badusers; Xprocedure Xinit() { X badusers = fresh\_database( 5 ); X badusers["bad@foo.bar"] = -5; X badusers["worse@site.com"] = -10; X badusers["worst@ihate.edu"] = -20; X} Xprocedure Xarticle() { X /* this adjusts score by 0 if not found */ X adjust badusers[from]; X /* and other code, of course */ X} X.Be XThe above scheme needn't be just a bad user database. You could assign Xpositive scores to good users, and they would be adjusted upwards using Xthis scheme. X.P XIf scores aren't your concern, you could also simply say X\fBreject if from in badusers;\fP X.P XThe \fBfresh\_database\fP function creates an empty database, Xin this case expected to hold an average of 5 indices. The 5 is not Xa hard and fast limit -- you could still have 100 or even 1000 indices, but Xdatabase use would then get very slow. The closer your guess is to the Xreal number, the more optimal your use of the database will be in memory Xspace and speed. X X.H 2 "Message-ids" X.P XNaturally, one thing you will want to keep a database of is article Xmessage-ids. The message-id is a string that is unique for every USENET Xarticle posted. Every followup has a line called \fBreferences\fP which Xindicates the message-ids of the articles to which this is a followup. X(If an article is a followup to a followup -- and there are far too many Xof these -- there will be two message-ids in the \fBreferences\fP array.) X.P XLet's update our program to include programmed control of a \fBmessages\fP Xdatabase. If an article comes in from a user in our \fBbadusers\fP database, Xwe will reject it, of course, but we'll also store its message-id in Xour messages database. Then we can reject the followups, too. X.Bb X extern userid from; X extern string message\_id X extern string array references; X database badusers; X database messages; Xprocedure Xinit() { X badusers = fresh\_database( 5 ); X messages = fresh\_database( 100 ); X badusers["bad@foo.bar"] = true; X badusers["worse@site.com"] = true; X badusers["worst@ihate.edu"] = true; X} Xprocedure Xarticle() { X extern int followup; X if( from in badusers ) { X messages[message\_id] = true; X reject; X } X reject if followup && references in messages; X} X.Be X.P XThis program has been set to simply reject based on the presence of a user Xor ``parent'' (\fBreferences\fP) in the database. With a little cleverness, Xyou could arrange to use the score system, so that all followups get the Xsame adjustment that their parent got when it was accepted or rejected. X.P X(We do mean cleverness, as the indexing feature doesn't work on an array Xlike \fBreferences\fP, and you would have to write your own loop to find Xthe first valid parent and its score.) X.P XNote that we don't try to use \fBreferences\fP until we know it is there Xand defined. The \fBfollowup\fP variable tells us this, although we Xcould also have tested if \fBreferences != nilarray\fP. X X.H 2 "Disk Files" X.P XAll this is interesting, but not very useful if you can't have databases Xremember things from session to session of news clipping. X X.H 3 "Reading in a Database" X.P XTo read a database from a file, use the \fBread\_database\fP function. XYou provide it with the filename of the database. X.P XDatabase files have a particular format that includes the integer field Xvalue, a date of last access/change and the string index. You may, however, Xcreate your own database files, or add lines to existing database files Xquite easily. Just add lines with nothing more than the index string. X.P X(You will read about the uses of the date of last access/change later.) X.P XIn fact, your first databases, unless they are created with newsclip programs, Xwill probably be simple files where each line is a database index string. XWhen you read in such lines, the integer value field will be set to 1, Xand the date of last access set to the current time and date. If you want Xto create databases with integer values other than 1, see the reference Xsection. X.P XIf you had a file \fB/tmp/baduser\fP that contained the lines: X.Bb Xbad@foo.bar Xworse@site.com Xworst@ihate.edu X.Be Xthen you could create our badusers database with: X.Bb Xbadusers = read\_database( "/tmp/baduser" ); X.Be Xinstead of the \fBfresh\_database\fP call. X.P XIf you create a database in this way, you should later write it out Xwith the \fBwrite\_database\fP procedure described below, so that Xthe lines get proper dates and the database is written out in the Xproper format. X.P XIf the database you try to read isn't present on the disk, you'll get Xan empty database -- the same as if you had coded \fBfresh\_database(30)\fP. X X.H 3 "Writing the Database" X.P XTo write out a database, you say: X.Bb Xwrite\_database( base, filename, oldest ); X.Be Xwhere \fIoldest\fP is a date value that represents the oldest record that Xyou want written out. X.P XSome databases, most notably those of message-ids and subject lines, Xwill contain elements that age with time. XThere is no point in keeping an old message-id in the database when nobody Xis referring to the original message any more. X.P XBy specifying a date argument, you can arrange for old, unused elements Xof the database to ``expire,'' and not get written out. X.P XEvery time a database element is created, changed, or referenced with array Xindexing or the \fBin\fP and \fBhas\fP operators, the access time for the Xrecord is Xupdated to the current time. If you want to expire all elements that have Xnot been used in a month, provide the date value for one month previous Xto the current date when you write out the database. For example, X.Bb Xextern datetime time\_now; Xwrite\_database( messages, "/me/mymessages", time\_now - month ); X.Be Xwill do the trick. As you might guess, \fBtime\_now\fP contains the Xcurrent date and time, and \fBmonth\fP is a special constant that Xcontains the number of seconds (dates are measured in seconds) in one Xmonth (approximately). X.P XIf you write out an empty or nil database and the database file doesn't Xcurrently exist, it will not be created. X.H 2 "Datbabase Filenames" X.P XYou may want to keep all sorts of databases. In particular, you may Xwant to keep databases on both the all-article or ``global'' level, along Xwith a variety of small databases on a per-newsgroup level. X.P XIf your database needs are small, you may find that database lookup is Xfast enough that you can keep all your database information in global Xdatabases -- even if the information is stuff that's likely to only Xoccur in one newsgroup like subject lines or message-ids. If your needs Xget complex, you may decide to maintain individual databases of such Xthings for some, or even all, of your newsgroups. X.P XTo help you do this, certain escape sequences can be used in database Xfilenames to let you read and write your database files to the right Xplace in the filesystem. All the escapes start with a tilde. X.P XThe most useful ones are a single tilde (\fB~\fP) which expands to your Xhome directory, and \fB~n\fP, which expands to the name of the current Xnewsgroup (\fBmain\_newsgroup\fP) with dots mapped to slashes, the way Xthey are in the news spool directories. RN keeps the kill file for Xa group like ``rec.humor'' in \fB$HOME/News/rec/humor/KILL\fP. You can Xdo the same by using a filename like \fB~/News/~n/badsubjects\fP. If Xyour home is \fB/u/brad\fP and the newsgroup is ``rec.humor,'' this Xexpands to \fB/u/brad/News/rec/humor/badsubjects\fP, which would be Xyour bad subjects database for the newsgroup rec.humor, of course. X.P XOther escapes are possible. They are defined in the reference section. X X.H 3 "Newsgroup Specific Databases" X.P XTo make use of such databases, you will want to use the \fBstartgroup\fP Xand \fBendgroup\fP procedure ``entry points'' of a NewsClip program. X.P XAs you learned earlier on in the manual, NewsClip programs allow Xa variety of entry point procedures, most of them optional. XThese two can come in handy now. X.P XThe \fBstartgroup\fP procedure is executed whenever processing of a Xnew newsgroup starts. That new newsgroup will be stored in the Xvariable \fBmain\_newsgroup\fP. Here is where you can initialize Xthings for the newsgroup -- such as reading in newsgroup specific databases. X.P XThe \fBendgroup\fP procedure is called when processing of a newsgroup is Xfinished. X.Bb X database groupkill; X extern string subject; Xprocedure Xstartgroup() { X groupkill = read\_database( "~/News/~n/subjects" ); X} Xprocedure Xendgroup() { X extern datetime time\_now; X write\_database( groupkill, "~/News/~n/subjects", time\_now - 3*week ); X free\_database( groupkill ); X groupkill = nildatabase; X} Xprocedure Xarticle() { X if( drop\_re( subject ) in groupkill ) X reject; X} X.Be X.P XThis program maintains databases of subject lines that you don't want to Xsee in each newsgroup. There doesn't have to be a file for each newsgroup. XIf the file is missing, an empty database will be created that matches Xnothing, and no file will be written out at the end. X.P XYou will note that after writing out the database, expiring any items that Xhave not been seen in 3 weeks, we called the \fBfree\_database\fP procedure. XUnlike most items allocated in a NewsClip program, databases do not Xstay in temporary memory -- they have to last from article to article. X.P XIf you're reading in a database for each group and getting rid of it when Xthe group is done, you will want to free the memory it uses. Whenever Xyou free the memory used by a database, you should set the descriptor Xthat used to refer to that database to \fBnildatabase\fP, so that no Xfurther attempts are made to refer to it. X.P XAnother function has been introduced here -- \fBdrop\_re\fP. This function Xis intended for use on subject lines. It removes any ``Re:'' prefixes Xfrom the subject line. If you are storing subject lines in a database, Xyou will want to do this without any ``Re:'' prefixes that might get Xadded by followup software. When you search, you will want to remove Xthese prefixes as well. X.H 4 "File Existence" X.P XIt will often be the case that local newsgroup databases will not be Xpresent for all databases. While attempts to read such non-existent Xfiles produce empty databases rather than errors, you may wish to avoid Xeven that. X.P XWith the {fun} exists function, you can test if a file exists at all. XYou might wish to set flags if the database is not found. X.Bb Xif( !exists( "~/News/~n/bigbase" ) ) X dontscan = true; X.Be X.H 3 "Creating Directories" X.P XUnfortunately, the \fBwrite\_database\fP procedure is not able Xto create directories for files as needed. If you write to a database Xusing \fB~n\fP in the filename, the write might well fail because Xthe directory structure for ``comp/sys/ibm/pc'' (for example) might not Xexist in your database directory. X.P XYou must create such directories by hand. If you are using the RN Xnewsreader, you will find that its ``Ctrl-k'' command (for editing Xkill files) will create such directories for you. (It then places Xyou in the editor to edit your kill file, but you can quit without Xwriting out your file and the directories will still be created.) X.P XIf you don't wish to have to create directories, you can use the X\fB~N\fP code instead of the \fB~n\fP code. It creates a name Xthat fits in a single filename segment, doing the best it can in the Xlimited (usually to 14 characters) space. X.P XIf the entire newsgroup name will fit in a file name, it is unaltered, Xand no check character is added. X.P XFor example, for ``comp.sys.ibm.pc'' you might get the file name X``\fBHCp.sys.ibm.pc\fP'' where ``H'' is a ``checksum'' character. X.P XThis will normally be unique, but there is no guarantee of it. As new Xnewsgroups are created, two might collide. Use this form at your own Xrisk. X.H 2 "Database As Strings" X.P XThe normal purpose of the database is the high-speed ``hashed'' lookup Xof exact search strings. As databases are the only items which XNewsClip programs can read and write to disk, a few other features Xare available. X.P XIt is possible, for example, to search all the indices in a database Xwith the \fBhas\fP operator. If you write \fBdb has "a.*b"\fP, you Xwill be told if any of the index strings contain that pattern. X.P XIt is also possible to use a database as a group of pattern strings. If Xyou use a database on the \fIright hand\fP side of the \fBhas\fP operator, Xthen all the index strings in the database will be used as patterns to Xsearch your text area. X.P XThis allows you to keep a file of search patterns on disk, rather than Xhard coding them into your program. Searching in this way is somewhat Xslower than searching for patterns hard coded into the program, however. XIn some ways, keeping a database of search patterns on disk is the closest Xthing to an RN kill file. X.P XNote that when you read patterns from disk, you don't need four backslashes Xto match a single literal backslash, the way you do with constant patterns Xin your program. Two backslashes will do. X.H 2 "Database FOR Loop" X.P XYou can scan through all the strings in a database with NewsClip's special Xvariant of the \fBfor\fP loop. The syntax is: X.Bb Xfor( \fIstringvar\fP in \fIdatabase\fP ) X \fIstatement\fP; X.Be X.P XThis will cause a loop that loops through the database. On each iteration, Xthe string variable will have a different index value from the database. XThere is no particular pattern to the order that the database is traversed, Xbut you will get each index once and only once. X.P XFor example, you could count the number of matches of a pattern in a database Xlike this: X.Bb Xint matches; Xstring dbstr; Xdatabase db; Xdb = read\_database( "myfile" ); Xmatches = 0; Xfor( dbstr in db ) X if( dbstr has "a.*b" ) X matches++; X.Be X.P XWhen performing such a loop, it is possible to get the integer value for Xthe index in the normal way, with \fBdb[dbstr]\fP in this case. This Xis not particularly efficient, however, as this special \fBfor\fP loop Xis intended to scan the strings of the database, not the values. X X.H 2 "Database Uses" X.P XYou can use database as ``kill files,'' or as their reverse -- what Xyou might call ``keep files.'' It is possible, however, to do much Xmore. As noted, databases can remember scores, and you can assign Xthose scores to articles that have fields in your databases. X.P XYou may wish to keep lots of database files, or just a few. If you Xaren't using the database index integer value fields for anything, Xyou can combine several databases in one database by varying the value Xfield. You might make it 1 for message-ids to keep, -1 for message-ids Xto reject, 2 for users to accept, -2 for users to reject and so on. XSuch strings are unlikely to ever overlap. X.P XIf you don't want to keep individual databases for individual newsgroups, Xyou can also concatenate newsgroup names and the key strings together before Xadding to and searching the database. The \fBconcat\fP function helps you Xdo that. X.P XYour system manager might also keep system databases associated with Xnewsgroups in the news spool directories, the news library directory or Xeven the newsclip library directory. Check local system documentation for Xinformation. X.P XIf you do not use the automatic \fBxref\fP feature, Xyou can use databases to avoid processing crossposted articles more than Xonce. If you see a crossposted article during a session, you might record Xits message-id in a database, and then check to avoid processing articles Xalready in the database. Normally you just import the \fBxref\fP variable Xon machines that keep \fBXref:\fP lines in their articles. X.P X(Alternately, we suggest rejecting articles where the first group in Xthe \fBnewsgroups\fP array that is a \fBnewsrc\_group\fP is \fB\fP not Xthe \fBmain\_newsgroup\fP.) X.P XThe nice thing about the database is that other programs, such as your Xnewsreader, can easily update your database files. Usually all it takes Xis adding a line to the end of the file containing the string you want to add. XYou may be able to program macros in your newsreader that stick records into Xyour news filter databases with the touch of a key. X X.H 2 "Notes" X.P XAssigning to a database index creates an index if it isn't already Xpresent in the database. You must thus be careful to only make assignments Xwhen you actually want the entry to be in the database. Don't Xassume that assigning an integer value of 0 will stop the index from Xbeing present in the database. X.P XIf you do assign 0 to an index, you will of course get 0 back when you Xtry to get the value of that index. That is also what you get back when Xan index isn't there. The only way to tell the difference between an Xindex that isn't there and one with a value of zero is the \fBin\fP Xoperator. We advise in general against using the value zero. X.P XIt is possible to use the increment (\fB++\fP) and decrement (\fB-\-\fP) Xoperators on a database index. If the index doesn't exist, it gets Xcreated at 0, and then incremented (to 1) or decremented (to -1). X.P XIf you ask whether any element of an array exists \fBin\fP a database, Xonly the access date Xon the first entry in the array that was found will be updated. XIf you ask whether a string or strings match a database full of patterns, Xonly the first pattern in the database that matches has its access time Xupdated. X.P X``First'' by the way, is hard to predict, as the patterns are Xtried in an internal database order that has no relation to how the Xitems were entered into the database, or the order they appear in Xdatabase files. X.P XWhile it's not usually called for, you can delete and free up individual Xindices from the database with the \fBdb\_delete\fP procedure. X.Bb Xmydb["foo"] = 100; Xdb\_delete( mydb, "foo" ); X.Be X X.H 3 "Message IDs" X.P XMost of the examples above were done with message-ids. Unfortunately, Xdue to human error and the wide variety of posting software that exists, Xnot all followup messages contain a \fB: References\fP line with a proper Xlist of parent articles. X.P XThis means that if you filter using message ids, you still may not get Xexactly what you want. X.P XThe other alternative is to use subjects. Followups usually have Xa \fBSubject:\fP line that consists of the original article with X\fBRe:\fP prepended. If there is buggy software, there might be Xmore than one \fBRe:\fP, but this is usually not the case. X.P XThis is still not perfect, because may people who do followups generate Xnew subjects. This is, in fact, a wise move when the subject of Xa stream of articles drifts away from the original topic. If the X\fBreferences\fP array were always correct, this would not be a problem. X.P XYou can store subjects in a database and search for them just like Xmessage-ids. Just be sure to apply the \fBdrop\_re\fP function to Xany subject lines you work with. X.P XYou can also store subject lines in a database that you intend to use Xas a database of patterns. This will catch messages where the original Xsubject is still present, but has been expanded upon. It's slower, but Xwill catch more articles. If you do this, be sure to apply the X\fBliteral\_pattern\fP function as well as the \fBdrop\_re\fP Xfunction before storing the subject in your database of patterns. X.Bb Xextern string drop\_re( string ); Xextern string literal\_pattern( string ); X/* to store */ Xdatab[ literal\_pattern( drop\_re( subject ) ) ] = true; X/* to check */ Xreject if subject has datab; X.Be X.P XPerhaps you will find it best to work with both subjects and Xmessage-ids. X X X X.H 1 "Declarations" X.P XWe have already discussed a few types of declarations, such as variables, Xexternal variables and entry point procedures. X.P XTo summarize, global declarations can appear anywhere in the program, outside Xthe bodies of procedures and functions. X.P XLocal declarations can appear inside procedures and functions, at the very top. X.P XVariable declarations consist of a type name, the optional \fBarray\fP Xclassifier and the name of the variable. External variables can be Ximported by preceding the above with \fBextern\fP. X.H 2 "External Functions" X.P XIt is also possible, and usually necessary, to make external declarations Xfor functions and procedures. All the functions we have described to you Xso far have been special ``predeclared'' functions. You do not have to Xwrite external declarations for predeclared functions, and so you have Xgotten along fine so far. X.P XA sample external declaration looks like this: X.Bb Xextern string left( string, int ); X.Be X.P XYou can make an external function declaration at either the global level Xor the local level. Global external declarations last for the rest of Xthe program. Local ones exist only inside the routine they're in. XYou can't make an external declaration for one of your own functions. XIf you have to use one of those before you declare it in the program, you Xmake a forward declaration. X.P XThe general syntax is: X.Bb Xextern \fItype\fP \fIfuncname\fP( \fItype, type, ...\fP ); X.Be XThe first type is the return type. In the parentheses, you give a list Xof types for the arguments. You must provide a type for each argument. X.P XThere are many external functions available in the NewsClip library, Xmany more in the C library, and you can also define your own. X X.H 2 "Local Procedures & Functions" X.P XProcedures and functions are almost identical in form. Functions return Xa value, and the type of value they return must be declared. Procedures Xdon't return values, so the word \fBprocedure\fP is placed where you Xwould put the return type. Two examples are: X.Bb Xprocedure Xbumpcount() X{ X counter = counter + 5; X} Xint Xsquare( int value ) X{ X return value * value; X} X.Be X.P XAs you can see, procedures and functions can have an optional list of X\fIarguments\fP provided. In this list, you will name arguments with Xa type and an argument name. Those arguments can be used like variables Xwithin the subroutine. X.P XThe details of procedures and functions are complex. If you are not Xalready familiar with C programming, we do not advise that you make use Xof procedures and functions, other than in defining the simple entry Xpoint procedures shown in the examples. In this section, we will discuss Xonly the fine points of procedures and functions, assuming that the reader Xhas a knowledge of C or some similar programming language. X.P XThe other main difference between procedures and functions is the X\fBreturn\fP statement. In both cases, \fBreturn\fP makes the subroutine Xterminate immediately. XInside a function, the \fBreturn\fP statement must be given an argument, Xand that argument must be of a type that matches the declared return type Xthat came before the function name. X.P XInside a procedure, the \fBreturn\fP statement must not be given an Xargument. It just triggers termination of the procedure. X.P XThe \fBaccept\fP and \fBreject\fP statements may only be used in Xprocedures. In fact, they should only be used in procedures that Xhave been called only within procedures. While this rule is not Xstrictly enforced, if your \fBarticle\fP procedure calls a function that Xcalls a procedure that does a \fBreject\fP, then the score will indeed Xbe set to a very negative number, but processing will not be Xterminated immediately. X.P XYou have, of course, already been using procedures, as the main Xentry points like \fBarticle\fP are actually procedures with no arguments. X.P XArguments must be declared, and user subroutines can only take a fixed Xnumber of arguments. Argument types must match or be compatible when Xcalling a subroutine. Automatic type conversion is done, unlike in C. XFor example, if you pass a newsgroup variable to a subroutine that takes Xa string argument, the newsgroup name string will actually be passed. X.P XArguments are passed by value only. This means that subroutines can Xuse their arguments like variables, but they can't change the value Xof an argument variable for the caller, and thus can't pass values back Xexcept via the function return type. Sadly, procedures which wish to pass Xvalues back are limited to setting global variables. X.P XAll procedures and functions must be declared in advance, except for Xa special list of predeclared routines that are part of the NewsClip Xlanguage. You may not use an undeclared function and assume it returns Xan integer, as you can in C. X X.H 2 "Forward Declarations" X.P XIf you plan to use a procedure before it is defined in your program Xfile, you must make a forward declaration. A forward declaration looks Xjust like an \fBextern\fP declaration, except the keyword \fBforward\fP Xappears in place of \fBextern\fP. X.P XFor example, if a function is defined as: X.Bb Xstring Xrepeat( int n, string str ) X{ X int i; X extern string concat( string, string ); X string ret; X ret = ""; X for( i = 0; i < n; i++ ) X ret = concat( ret, str ); X return ret; X} X.Be XThen a forward definition would look like: X.Bb Xforward string repeat( int, string ); X.Be X X.H 2 "Your Own Headers" X.P XWhile there is a predefined header variable that you can import for Xjust about every known USENET header, you may still wish to define your Xown header variables. You can, with a special declaration. In place of Xa regular declaration, say: X.Bb Xheader \fItype\fP \fIvarname\fP : "\fIkeyword\fP"; Xor Xheader \fItype\fP array \fIvarname\fP : "\fIkeyword\fP", "\fIdelims\fP"; X.Be X.P XThe header line prefaced by the \fIkeyword\fP will be processed and Xstored into your Xvariable. If it's an array variable, the field will be parsed with the Xdelimiters you specify in the delimiter string. Any span of the Xdelimiter characters counts as one delimiter, by the way. Typical Xdelimiters are space, tab and comma. X.P XYou can use this to define new header lines that appear but aren't supported Xby NewsClip yet. You can also make your own definitions for standard Xheader lines, so long as you don't try to import the official header Xvariable. The only header line you can't make your own definition for Xis \fBnewsgroups\fP -- that's always a newsgroup array. X.P XYou can, for example, define your own \fBXref:\fP header line, thus Xavoiding the automatic processing that comes when you import the X\fBxref\fP variable. X.P XThere is a special trick involving the \fIkeyword\fPs. If your keyword Xstring starts with a lower case letter, then the field will be mapped Xto lower case before being parsed. If it starts with an upper case Xletter, the mapping doesn't take place. Almost all the predefined header Xvariables include mapping to lower case, as it's better for string comparisons Xand pattern matching. X.P XYou can thus use your own header declarations to stop the lower case Xmapping done normally in NewsClip. X.Bb Xheader string subject : "Subject"; X.Be Xwould give you a new subject variable. While this example shows the Xnew variable replacing the old, we advise that you give new variables Xdifferent names when possible, so as to avoid confusion. X.Bb Xheader string upsubject : "Subject"; X.Be X.P XAnother special trick involves the delimiter string. While you can include Xspace as a delimiter character, that doesn't allow you to break up things Xlike multi-word lists split up with commas. If you put an \fBS\fP at the Xfront of the delimiter string, the S does not become a delimiter. Instead Xit signals that white space should be removed from the front and end of Xelement strings. This is how the \fBKeywords\fP array is parsed, by default, Xusing a delimiter string of \fB"S;"\fP to do the job. X.P XHeader declarations may only appear as global declarations. You can not Xplace them inside subroutines. X X X X.H 1 "General Notes" X.P XIn this chapter we describe some of the special functions, procedures Xand variables available for use in NewsClip programs. Sometimes the Xdescription here is just an introduction. You should check the reference Xsection for more details. X X.H 2 "Distribution" X.P XThere may be times when you wish to examine an article based on how Xfar it was intended to be distributed. This is of use in filtering feeds Xto other sites, but it is also useful to readers who want to give priority Xto articles posted to a local subnet of a worldwide group. X.P XUSENET distribution is normally controlled by the \fBNewsgroups:\fP line, but Xdistribution can also be restricted by proving a further list of groups Xon a \fBDistribution:\fP line. X.P XIt is worth noting that while the names used on the \fBDistribution:\fP line Xare usually thought of as special distribution keywords, they are actually Xspecial newsgroups. Thus \fBdistribution\fP is a newsgroup array. In Xfact, you can use regular newsgroup names as distributions if you want. XAn article posted to ``comp.misc'' with a distribution of ``rec.humor'' Xwould only go to machines that get both. X.P XUsually on USENET the higher level ``root'' names are used to define Xhierarchies of newsgroups as well as distribution. ``Comp'' is a newsgroup Xthat nobody posts to, but any machine that is fed ``comp'' will be fed all Xgroups that are in the ``comp'' hierarchy. X.P XWe tell you all this because the USENET distribution mechanism is not Xwidely understood, but it's important to know about it to filter articles Xwith it. X.P XIf the \fBDistribution:\fP line is missing, then the \fBNewsgroups:\fP line Xdoubles as the distribution. As such, we advise you not to use the X\fBdistribution\fP variable, but rather the special \fBrdistribution\fP Xvariable. This variable gives the \fBdistribution\fP array if that is Xpresent, and gives \fBnewsgroups\fP otherwise. As such it is always Xdefined and correct. X.P XYou can check to see if an article has been explicitly limited to a Xdistribution like ``usa'' by using an expression like X\fB#usa in rdistribution\fP, remembering that ``usa'' is a newsgroup name Xconstant. It is more likely you will want to check for anything that Xstarts with usa, so you might rather use \fBrdistribution has "^usa"\fP, Xwhich uses pattern matching. X.P XNormally, however, you just want to check the distribution level -- is it Xlocal, citywide, statewide, national or international? In other words, Xabout how many machines was the article posted to? X.P XAssuming the NewsClip system has been installed properly, every newsgroup Xand distribution (remember that distributions are just special newsgroups) Xwill have an integer \fIdistribution level\fP associated with it. XYou can get this level with the function \fBdlevel(group)\fP. X.P XThis number is an estimate of the number of machines that get Xthe given group or distribution. It is by no means guaranteed to be Xanywhere near accurate. What is important is that the numbers are Xordered, and that wider distributions will have a higher distribution Xlevel number than small ones. This means that you can do comparisons. X.P XTo help in this, we have defined some special fake newsgroups which Xexist solely to be used as arguments to \fBdlevel\fP. They are names Xlike \fB#local\fP, \fB#organization\fP, \fB#city\fP, \fB#region\fP, X\fB#state\fP, \fB#province\fP, \fB#country\fP, \fB#continent\fP, X\fB#usenet\fP and \fB#world\fP. They will be defined to be the Xright numbers for the distributions at your site that match these Xgeographic regions. X.P XIf you want to see how far a group is going, you can ask something Xlike: X.Bb Xif( dlevel(group) >= dlevel( #state ) ) X.Be Xand that should tell you if the group is statewide or larger. Of course Xyou can also hard code your local statewide distribution if you want Xto. X X.H 3 "distribution\_level" X.P XThe key to all this is a special variable called \fBdistribution\_level\fP. XIt is the distribution number for the current article. It is calculated Xby looking at all the groups on the \fBDistribution:\fP and \fBNewsgroups:\fP Xlines. X.P XIf you want to see all the articles posted for within your city, you can Xsay: X.Bb Xaccept if distribution\_level <= dlevel( #city ); X.Be X.P XYou can also assign points as you like based on distribution, assigning Xmore or less to the score based on how wide the article was meant to go. XAnd of course, you can do it on a group by group basis. X.P XThis is similar to another trick, which shows you articles posted by Xlocal people. If you're in the large domain ``foo.edu,'' you can ask to see Xall articles posted by locals with X\fBaccept if right(from,2) == "foo.edu";\fP or X\fBaccept if from has "foo.edu$";\fP, whichever you prefer. Since Xall articles restricted to your domain normally come from people within Xthe domain, this shows you the local articles. It also shows you the Xworldwide articles posted by local people. X.P X(The above only works if ``foo.edu'' is never used as a name on its own. XUse \fBright(domain(from),2)\fP if ``user@foo.edu'' might exist.) X X.H 2 "Special Header Variables" X.P XIn the previous section, we saw the variable \fBrdistribution\fP which Xgives you the ``real distribution.'' There are similar variables for Xall the other header items which have defaults. \fBRreply\_to\fP Xand \fBRsender\fP default to \fBfrom\fP if their main header line is Xmissing. \fBRfollowup\_to\fP and \fBRdistribution\fP default to X\fBnewsgroups\fP. X.P XYou have also seen some calculated variables that depend on header Xlines. These include \fBdistribution\_level\fP, described above, Xand \fBfollowup\fP which is true if the article has a X\fBReferences:\fP line. X X.H 2 "The Outside World" X.P XA few variables you can import come from places beyond even the Xheader. For example, when you invoke the news clipping program from Xthe shell, you can give options that begin with the string \fIo=\fP. X(You can spell it out as \fIoption=\fP if you like.) The arguments Xof these options are placed in the string array \fBoptions\fP. X.P XThus if your program is called \fBnclip\fP and it gets called with: X.Bb Xnclip o=quick o=+j X.Be Xthen the variable options will have two values, ``quick'' and ``+j.'' XYou can test for the presence of one with an expression like X\fB"quick" in options\fP. X.P XAside from options, you can also examine environment variables with the Xstring function \fBgetenv\fP. You might tell users to define an Xenvironment variable ``CLIPOPTS''. A call to \fBgetenv("CLIPOPTS")\fP Xwould return the string they provided, or \fBnilstring\fP if the environment Xvariable were not defined. X.P XThere will be times when you want to check for messages from people Xat your own site, and particularly for messages from yourself. While Xyou can code this explicitly with something like \fBfrom == "me@mysite"\fP, Xwe have provided two external variables that contain the mail address and Xthe site/domain of the person running the newsclip program. X.Bb Xextern string my\_domain; Xextern string my\_mail\_address; Xif( domain(from) == my\_domain ) { X adjust 20; X if( from == my\_mail\_address ) X adjust 100; X } X.Be X.P XYou will want to use these variables if you are writing newsclip programs Xor subroutines for use by other people. X.P XIn the above example, you may have noticed the string function \fBdomain\fP, Xwhich returns the string after the first at-sign (@) in the argument it Xis given. When passed a \fBuserid\fP variable, this gives the \fIsite\fP Xor \fIdomain\fP part of the address. X.P XNote that there is no way to find out the local machine's full domain Xname under program control, so these variables will only be valid if the Xperson who installed the NewsClip system set them up properly. X.P XAnother useful system value is the \fBdatetime\fP value \fBtime\_now\fP, Xwhich gives the current time, or at least the time when your news clipping Xprogram started running. You have already seen this used in the X\fBwrite\_database\fP examples. X X.H 2 "Nil Variables" X.P XHeader variables that are not defined for an article, along with Xuninitialized global variables all have what are known as X``nil'' values. (Uninitialized local variables have undefined values -- Xyou can't be sure at all what they are.) X.P XBefore using a header variable that might not have been set for an Xarticle, you should always check first to see if it is nil. If you Xtry any operators or nil arrays, strings or userids, you may crash your Xprogram -- particularly if you assign into an index of a nil or undefined Xarray variable. X.P XRemember, all header variables are set to 0 or nil with each new article. X.P XUnlike in C, you must explicitly compare your values with special nil Xconstants. You can't just use an array variable alone in an if, as in: X.Bb Xif( arrvar ) X whatever; X.Be X.P XInstead use: X.Bb Xif( arrvar != nilarray ) X whatever; X.Be XThere are similar values known as \fBnilstring\fP, \fBniluserid\fP and X\fBnilnewsgroup\fP. Nil integers and dates are set to zero. X.P XThere is also a special value called \fBnildatabase\fP. If you try to Xindex into a nil database to read values, you will always get 0, as though Xthe index were not found. Any attempt to store into a nil database or Xfree one will generate an error. X X.H 2 "Global Control & Subscription" X.P XRight now, newsreading programs usually handle the details of what newsgroups Xa person subscribes or does not subscribe to. In general, this means that Xthe NewsClip program's only job is to filter articles in subscribed Xnewsgroups. X.P XThis doesn't have to be so, of course. For example, if your \fBarticle\fP Xprocedure were to include a line like: X.Bb Xreject if is comp.misc; X.Be Xthat would effectively ``unsubscribe'' you to that group. It would Xalso reject all crosspostings from that group into groups you read. X.P XWe have provided a more efficient way of doing this, by providing some Xvariables you can set in the \fBstartgroup\fP procedure of your clipping Xprogram. X.P XThese variables are called \fBreject\_all\fP and \fBaccept\_all\fP. If Xyou set \fBreject\_all\fP in your \fBstartgroup\fP procedure, that is the Xsame as unsubscribing to a group. All articles will be rejected until Xwe are finished processing that group. X.P XThis is more efficient than including the sample \fBreject if\fP statement Xabove, for if \fBreject\_all\fP is set, then the articles don't even get Xlooked at. They are rejected out of hand. These two special variables Xare reset whenever a clipping program finishes with a newsgroup, so they Xonly apply to one newsgroup at a time. X.P XThe variable \fBaccept\_all\fP does the reverse. All articles in the Xgroup are accepted, without even being examined. This effectively turns Xoff the use of your \fBarticle\fP filtering procedure for the duration of Xthat group. X.P XYou could set up to unsubscribe to groups with code like: X.Bb Xprocedure Xstartgroup() { X extern newsgroup main\_newsgroup; X extern int reject\_all; X switch( main\_newsgroup ) { X case #comp.misc: X case #news.software.b: X case #talk.politics.misc: X reject\_all = true; X } X X} X.Be X.P XOf course, you could also do the reverse of this, by doing nothing for Xthe groups named in the \fBcase\fP statements, and turning on X\fBreject\_all\fP by default. X.P XThe use of these variables can be important in the \fIpipe\fP mode, where Xyour program talks to a newsreader. By setting one of these two special Xvariables, you tell that newsreader that it doesn't have to even talk to Xthe news filter program for the duration of a newsgroup. X.P XThese variables should not be used at all in \fIfilter\fP mode, because in that Xmode there is no such thing as a ``current newsgroup.'' X X.H 3 "Named Groups" X.P XOne good idea is to set \fBaccept\_all\fP for all groups that aren't Xactually named in your program. If the group isn't named in your Xprogram, you probably don't care to scan it. There's a handy function Xto do this for you. X.Bb Xextern newsgroup main\_newsgroup; Xextern int accept\_all; Xextern int named\_group( newsgroup ); Xif( ! named\_group( newsgroup ) ) X accept\_all = true; X.Be XA ``named group'' is one that you named as a newsgroup constant X(\fB#rec.humor\fP) or inside an \fBis\fP operator. X.P XIf you deal with groups in general, for example by checking for Xthe ``comp'' prefix on the front of the group name, such groups will Xnot be considered named groups. You will have to add checks for such Xgroups to ensure you don't set \fBaccept\_all\fP on them. X.P XAs you might guess, \fBnamed\_group\fP returns true if the group is Xa named one, and false otherwise. X.P XThis is a very useful thing to do when working with a newsreader in Xpipe mode, as it will know to not bother sending you messages in groups Xyou don't wish to filter. X X.H 2 "String Tools" X.P XTo help you manipulate the strings that show up in news article Xheaders, a number of special string functions are available. Most Xof these have to be imported as externals. X.P XIt is important to note that string values are allocated in what we call Xa ``temporary'' pool of memory. That means that the memory for all normal Xstrings is erased and re-used with each new article. If you assign a Xstring variable during the processing of one article, you can't expect Xit to be around for the next one. X.P XThis is usually only a problem for code in sections outside the X\fBarticle\fP procedure. Strings created in the \fBstartgroup\fP procedure Xstill go away with the first article. If that isn't what you want, you Xcan create permanent strings with the \fBpermstring\fP function. Just Xsay \fBstr = permstring( tempstr )\fP to make a string permanent. The Xtwo variables can even be the same. X.P XDon't make too many strings permanent, or you will run out of memory on Xsome machines. X X.H 3 "Dots & Domains" X.P XMany key strings on USENET are delimited with dots. In Xparticular, newsgroup names and domain names are split into parts this Xway. X.P XWe have provided two functions to take out substrings from such names. XThe \fBleft\fP and \fBright\fP functions give you left or right parts Xof such strings, delimited with dots. You provide a string and the Xnumber of parts you want. X.P XFor example, \fBleft( "comp.sys.ibm.pc", 1 )\fP is ``comp'' and X\fBleft( "comp.sys.ibm.pc", 2 )\fP is ``comp.sys,'' as you might Xexpect. \fBRight( "me.my.cs.edu", 1 )\fP is ``edu,'' the top level Xdomain. X.P XYou can check for left and right parts with pattern matching, but often Xthe use of \fBleft\fP and \fBright\fP is more efficient. If you ask for Xmore parts than there are, you get the whole string. X.P XQuite often when you get an email address from a field like \fBfrom\fP, Xyou wish to examine just the ``site name'' or ``domain'' part of it. XThe \fBdomain\fP function does this for you. For example, X\fBdomain("user@foo.bar.com")\fP is ``foo.bar.com.'' X X.H 3 "Length and Indexing" X.P XYou can get the length of a string with \fBstrlen\fP. It returns an Xinteger. You can index any character in a string, from character 0, Xto the character at position \fBstrlen(string)-1\fP with the X\fBchindex\fP function. X.P X\fBChindex("ABCD", 2)\fP is the integer 67, which is the ascii code Xfor the letter ``C.'' You can express character constants in single Xquotes if you wish to do comparison. You will find expressions like X\fBchindex(mystr, 3) == 'S'\fP can do the trick for you. X.P XYou can't assign characters into a string. You can just read them out. X.H 3 "Subjects" X.P XMost USENET subject lines are from followups, and as such they have Xone or more instances of ``Re:'' on the front. The function X\fBdrop\_re\fP takes a subject string, and returns the string without Xany spaces or ``Re:'' prefixes on the front. In many programs, this is Xthe string you want to examine, so you will see things like: X.Bb Xextern string subject X{procgap} Xstring realsubject; Xrealsubject = drop\_re( subject ); X.Be Xat the front of \fBarticle\fP procedure, all subsequent tests are Xdone on the variable \fBrealsubject\fP. X.H 3 "Concat" X.P XYou can concatenate two strings together with the \fBconcat\fP function. XIf you want to concatenate more than two strings, just call \fBconcat\fP Xseveral times. For example, X.Bb Xallthree = concat( concat( s1, s2 ), s3 ); X.Be Xworks fine to concatenate three strings. X.P XYou can use this to build filenames or database keys or whatever you Xlike. X X.H 2 "Cross Posting" X.P XMany articles are cross posted to several groups. In our typical Xprogram, we did a loop so that our group-specific filtering Xroutines were performed for each group in the list. You may or may Xnot always wish to do this. X.P XIf an article is cross posted to several groups you read, you usually Xdon't want to see it twice. To arrange this, simply put in an import Xdeclaration for the \fBxref\fP variable. X.Bb Xextern string array xref; X.Be X.P XIf you do this, and you're running in the \fInewsrc\fP mode, then when Xa cross-posted article is rejected, it will get marked read in all the Xsubscribed groups in which it is found. X.P XThis is normally what you want, but it is also not the default. If, Xas suggested in the chapter on a typical program, you Xdo a loop that checks all groups an article is posted to, then if an Xarticle is rejected in one group, it will be rejected in all the others Xas well, as it runs through the same code. NewsClip is fast enough Xthat this may be all you need to do the job. X.P XIf you don't run the articles through the same code, you may get articles Xaccepted in one group and rejected in another. Sometimes this can Xbe what you want. Remember that many modern newsreaders make sure that Xthey don't show you the same article more than once. By having individual Xcontrol over what groups an article is accepted in, you can control what Xgroup you read articles in. With most newsreaders, you will see the Xarticle in the first group in your reading order. X.P XIf you are producing a file list (ie. not running in \fInewsrc\fP mode), it Xis fairly important that the same article not be listed twice in two Xdifferent places. We advise that you import the \fBxref\fP variable in Xthis case. When you do, even articles that are accepted get marked off Xas read in the other groups, so that nothing gets listed twice. X.P XThere are other ways to eliminate crosspostings, of course. One suggestion Xon this is described in the database chapter. If your system does not Xsupport the \fBXref:\fP header line, you will need to code up such Xa system, and it may even be more efficient. X X.H 2 "Entry Points" X.P XYou are now very familiar with the \fBarticle\fP entry point procedure Xthat is called with each new article. Here is a complete list of Xthe entry point procedures. X.P XRemember that most of these entry points are \fIoptional\fP. That means Xif you create one, but spell its name wrong, you will get a procedure Xthat never gets called, and a null procedure where you wanted a real one. X.H 3 "Init" X.P XCode that is run when the newsclip session first begins. This Xallows the initialization of global variables. You will want to Xload up your global databases and set any of the special control Xvariables here, too. X.H 3 "Startgroup" X.P XCode that is run whenever a new newsgroup is processed. Here is where Xyou can test for and load newsgroup related databases. This procedure Xis given an argument, which is an estimate of the number of unread articles Xin the group. The estimate may not be very accurate, and will usually only Xbe good in \fInewsrc\fP mode. X.H 3 "Endgroup" X.P XCode that is run when we're done with a newsgroup. Here is where you can Xwrite out your newsgroup related databases and free them. X.H 3 "Article" X.P XCode that is run with each article. This code decides whether Xto accept or reject the article. The default is to accept. X.H 3 "Post_article" X.P XCode that is run after the \fBarticle\fP routine terminates. XSuch code can examine the score and take special further action. XThis procedure is given an integer argument, namely the score. (This Xis also available in the global \fBscore\fP variable.) X.H 3 "Terminate" X.P XCode that is run when the newsclip session is done. In this Xsection, one usually writes out any updated information to disk. X.H 3 "Command" X.P XCode that is run when a kill command comes down the pipe in \fIpipe\fP Xmode. This procedure gets a string argument, which is the command Xstring. It is expected to either \fBreject\fP the command, or process Xit an issue an \fBaccept\fP. X X.H 2 "Advanced Statements" X.P XThere are a few statements we haven't covered at this point. They are Xmeant for advanced users. Some come from C, others are new to NewsClip. XFull descriptions can be found in the reference section. X.H 3 "Control Flow" X.P XFrom C, we have included 4 basic control flow statements. \fBbreak\fP Xjumps out of an enclosing loop or \fBswitch\fP statement. \fBContinue\fP Xjumps immediately to the top of any current \fBfor\fP or \fBwhile\fP Xloop, so that the next iteration can be executed. X.P XFinally, there is the general \fBgoto\fP statement, which jumps to a label Xwithin a subroutine. XYou can label any statement by placing an identifier and a colon in front Xof it. X.P XThe \fBreturn\fP statement terminates a subroutine. Inside a function, it Xmust be given an argument that is the function return value. XIn procedures it must not be given an argument. The \fBaccept\fP and X\fBreject\fP statements are variants of \fBreturn\fP which set the X\fBscore\fP. X.H 3 "Dynamic Arrays" X.P XAdvanced users can create arrays of any size they choose. The array is Xallocated in temporary memory. Try: X.Bb X\fIarrayvar\fP = array \fIexpr\fP; X.Be Xwhere the \fIexpr\fP should be an integer expression giving the desired Xsize of the array. The array will index from 0 to \fIexpr\fP minus 1. XFor example: X.Bb Xstring array myar; Xmyar = array 20; X.Be Xcreates a string array with 20 elements, indexed from 0 to 19. X.H 3 "String Parsing & Type Conversion" X.P XYou can also access the same parsing routines that turn header fields Xinto header variables. For scalar (non array) variables, try: X.Bb Xparse \fIvar\fP = \fIstring-expr\fP; X.Be XThis converts the string into the right type and stores it in the variable. XYou can convert newsgroup names to newsgroup numbers, or dates to date Xvalues this way. It is also a handy way of converting a string to an Xinteger. X.P XThe array form is: X.Bb Xparse \fIarrvar\fP = \fIstring-expr\fP, \fIdelims\fP; X.Be XThe second string expression defines the delimiters that will be used Xto parse the array. The same rules apply here as for the \fBheader\fP Xdeclaration described elsewhere. For example: X.Bb Xnewsgroup array myn; Xparse myn = "rec.autos,news.groups", " ,"; X.Be X X X X.H 1 "Compiling & Operation" X.P END_OF_FILE if test 53631 -ne `wc -c <'doc/man.mm.2'`; then echo shar: \"'doc/man.mm.2'\" unpacked with wrong size! fi # end of 'doc/man.mm.2' fi echo shar: End of archive 15 \(of 15\). cp /dev/null ark15isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 15 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0