home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume13 / slice < prev    next >
Text File  |  1988-01-31  |  37KB  |  1,274 lines

  1. Subject:  v13i073:  Split file based on patterns or line numbers
  2. Newsgroups: comp.sources.unix
  3. Sender: sources
  4. Approved: rsalz@uunet.UU.NET
  5.  
  6. Submitted-by: Gary Puckering <cognos!garyp>
  7. Posting-number: Volume 13, Issue 73
  8. Archive-name: slice
  9.  
  10. Slice splits up a file into lots of little files.  It reads its input
  11. a line at a time, and starts a new output file when
  12.  
  13.     *  the input line matches a pattern, or
  14.     *  there have been n lines written to the current output file.
  15.  
  16. You can use it to split a mailbox or an archive of news articles into
  17. one article per file, for example.  In fact, you can do this with
  18. about 5 lines of awk, but you run into problems with long lines (and
  19. speed, if it bothers you!).
  20.  
  21. Slice was originally contributed by Russell Quinn as the program
  22. "mailsplit".  It was subsequently revised by me and posted to
  23. net.sources.  A patch for System V was also issued.  Since then, I've
  24. made quite a few changes, enough so that this version is not 100%
  25. compatible with the net.sources versions.  Also, I've not retested
  26. this version on System V, so there may be some problems.
  27.  
  28. Slice allows multiple output formats to be specified (rather than
  29. multiple input files).  This makes it possible to deposit the pieces
  30. (slices!) into files named whatever your want.  For example:
  31.  
  32.      slice -f article -x -r README '^--* [Cc]ut' article.sh
  33.  
  34. will deposit everything up to the cut line into README and everything
  35. after it into article.sh.  The -x option causes the matched line to be
  36. excluded.  The -r option specifies the destination of rejected lines
  37. (which in this example includes all lines appearing before the cut line).
  38.  
  39. There are even options to make slicing mailboxes and files containing
  40. shell scripts easier (-m and -s).  The mailbox option enables you to
  41. easily reorder messages in your mailbox into proper date sequence:
  42. just slice the mailbox with -m then cat the resulting files back
  43. together.  This neat trick is accomplished by the use of substitution
  44. parameters in the output pattern.  If an output pattern contains, say,
  45. a #3 then the 3rd token from each matched line is substituted into the
  46. file name.  Thus, to split a mailbox the default output pattern causes
  47. each message to be put in a file whose name is constructed from the
  48. date and time on the "From" line.
  49.  
  50. There are some good examples in the man page.
  51.  
  52. Source, Makefile and manual entry enclosed.  To install, do the
  53. following:
  54.  
  55. 1:    Edit the Makefile: you'll need to alter the "R=/usr/local" if 
  56.     you don't want slice to live in /usr/local/usr/bin.
  57.  
  58. 2:    make slice
  59.  
  60. 3:    have a play with it & satisfy yourself that it behaves reasonably
  61.  
  62. 4:    make install
  63.  
  64. Make "install" will do a "$(MAKE) $(CLEAN)" afterwards.  If you don't 
  65. want to remove the binary, say
  66.  
  67.         CLEAN="" make install
  68.  
  69. at step 4.
  70.  
  71.  
  72. --------------------- cut here ----------------------------------------
  73. #!/bin/sh
  74. # This is a shell archive, meaning:
  75. # 1. Remove everything above the #!/bin/sh line.
  76. # 2. Save the resulting text in a file.
  77. # 3. Execute the file with /bin/sh (not csh) to create the files:
  78. #    slice.1
  79. #    Makefile
  80. #    opts.h
  81. #    slice.c
  82. #    CHANGES
  83. # By:    Gary Puckering ()
  84. export PATH; PATH=/bin:$PATH
  85. echo shar: extracting "'slice.1'" '(10249 characters)'
  86. if test -f 'slice.1'
  87. then
  88.     echo shar: over-writing existing file "'slice.1'"
  89. fi
  90. sed 's/^X//' << \SHAR_EOF > 'slice.1'
  91. X.TH SLICE 1L "1987 September 14" "Cognos Inc."
  92. X.SH NAME
  93. Xslice \- split a file into pieces, by pattern or every n'th line
  94. X.SH SYNOPSIS
  95. X.B slice
  96. X.tr _-
  97. X[\|\fB_f\ \fIfilename\fP\|]
  98. X[\|\fB_i\fP\fIn\fP\|]
  99. X[\|\fB_a\fP\|]
  100. X[\|\fB_x\fP\|]
  101. X[\|\fB_r\fP\ \fIrejectfile\fP\|]
  102. X[\|\fB_\&m\fP\||\ \fB_\&s\fP\||\ \fB_\&n\fP\fIn\fP\|]
  103. X[\|\fIformat\fP\ .\|.\|.\|]
  104. X.LP
  105. X.sp
  106. X.B slice
  107. X[\|\fB_f\ \fIfilename\fP\|]
  108. X[\|\fB_i\fP\fIn\fP\|]
  109. X[\|\fB_a\fP\|]
  110. X[\|\fB_x\fP\|]
  111. X[\|\fB_r\fP\ \fIrejectfile\fP\|]
  112. X\ \fB_\&e\ \fIexpression\fP\ 
  113. X[\|\fIformat\fP\ .\|.\|.\|]
  114. X.LP
  115. X.sp
  116. X.B slice
  117. X[\|\fB_f\ \fIfilename\fP\|]
  118. X[\|\fB_i\fP\fIn\fP\|]
  119. X[\|\fB_a\fP\|]
  120. X[\|\fB_x\fP\|]
  121. X[\|\fB_r\fP\ \fIrejectfile\fP\|]
  122. X\ \fIexpression\fP\ 
  123. X[\|\fIformat\fP\ .\|.\|.\|]
  124. X.tr
  125. X.SH DESCRIPTION
  126. X.I Slice
  127. Xsplits a file into pieces (called \fIslices\fR) which are deposited
  128. Xinto one or more output files.  The output files are named according
  129. Xto the \fIformat\fR strings provided.  The input file is split
  130. Xwhenever a pattern is matched or every \fIn\fR lines, depending on the
  131. Xoptions selected.  Because some of the options are mutually exclusive,
  132. Xthere are three forms of the command.
  133. X.LP
  134. XWhenever a pattern match is used to slice the file, lines occurring
  135. Xbefore the first match are sent to the \fIreject\fR file (which is
  136. X\fI/dev/null\fR by default).  Lines are also rejected when an output file
  137. Xcannot be opened, for whatever reason.
  138. X.LP
  139. X.SH OPTIONS
  140. X.LP
  141. X.IP "\fB-f\fR \fIfilename\fR"
  142. XInput for \fIslice\fR is taken from the named file rather than \fIstdin\fR.
  143. X.IP "\fB-i\fP\fIn\fP"
  144. XThe starting number for numbering output files generated by formats
  145. Xcontaining "#\&n" (see \fISubstitutions\fR below).  The default
  146. Xstarting number is one.
  147. X.IP "\fB-a\fR"
  148. XCauses the file to be split \fBafter\fR the line matching the pattern,
  149. Xrather than before (as is normally the case).  This option will not
  150. Xwork properly if an output format contains the substitution parameters
  151. X"#\&1", "#\&2", etc.  (See \fIformat\fR below).
  152. X.IP "\fB-x\fR"
  153. XCauses the matched line to be excluded from the output files.  Handy
  154. Xfor eliminating cut lines, etc.
  155. X.IP "\fB-m\fR"
  156. XUses the pattern "^From " to split the file.  This is convenient for
  157. Xbreaking up a mailbox file.  It also changes the default output
  158. Xpattern so that the output files are named according to the date and
  159. Xtime of each message.  This makes it easy to sort the messages in a
  160. Xmailbox into chronological order \- just slice the box and cat the
  161. Xresulting files back together again.
  162. X.IP "\fB-s\fR"
  163. XUses the pattern "^#\&!\ */bin/sh" to split the file.  This is ideal for
  164. Xbreaking up mail or news article containing a Bourne shell script.
  165. X.IP "\fB-n\fIn\fR"
  166. XSplit the file every \fIn\fR lines.  In this case, no pattern matching 
  167. Xis performed.  This is the behaviour of \fIsplit (1)\fR,
  168. Xexcept that the default output filename format for
  169. X\fIslice\fR is different.
  170. X.IP "\fB-r\fR \fIrejectfile\fR"
  171. XEstablishes the name of a file to which rejected lines are sent
  172. X(default is \fI/dev/null\fR).  Lines appearing before the first matched
  173. Xpattern are rejected.  Also, any time an output file cannot be opened,
  174. Xsource lines are sent to the reject file instead.  Substitution
  175. Xparameters are not allowed in the reject file name, but a leading "+"
  176. Xis allowed (to indicated that the reject file should be opened for append).
  177. XThe name \fIstderr\fR can also be used for the standard error file.
  178. X.IP "\fB-e\fR \fIexpression\fR"
  179. XUses the pattern specified by \fIexpression\fR to identify where the
  180. Xfile is to be split.  The pattern may contain newlines (which match
  181. Xthemselves).  See
  182. X.I ed (1)
  183. Xfor details of the regular expressions.
  184. X.IP \fIformat\fR
  185. XAll three forms of the command allow the specification of zero or more
  186. X\fIformat\fR strings as non-flag parameters.  Formats are used to
  187. Xgenerate output filenames.  You can provide as many as you like.
  188. XEvery time a split is required, a format is used to generate the file
  189. Xname.  If the name is the same as the last name generated from that
  190. Xformat, a new format is selected.  If \fIslice\fR runs out of formats,
  191. Xa warning will be issued (unless the last output file is \fIstdout\fR)
  192. Xand the remaining lines will be sent to the reject file.  The format
  193. Xstrings can contain substitution parameters.  
  194. X.SH SUBSTITUTIONS
  195. X.IP "#\&f"
  196. XThe name of the input file, less any leading pathname, is substituted.
  197. XIf the input file is \fIstdin\fR, the name \fIslice\fR is used.
  198. X.IP "#\&n"
  199. XA unique number, beginning with the value specified in the \fB-i\fR
  200. Xoption, is substituted.  This number is incremented by 1 for each
  201. Xoutput file produced by the current output format.  When an output
  202. Xformat produces the same name twice, a new format is selected and
  203. Xnumbering begins again with the initial value.
  204. X.IP "#\&1, #\&2 ..."
  205. XParameters of the form #\&1, #\&2, ... #\&9 are replaced by corresponding
  206. Xtokens drawn from the source line which matched the slice pattern.
  207. XFor example, if each procedure in a C program began with a comment
  208. Xline of the following form:
  209. X.sp
  210. X\ \ \ \ /\&* Procedure:\ \ \ main */
  211. X.br
  212. X\ \ \ \ #\&1\ \ \ #\&2\ \ \ \ \ \ \ \ \ \ \ #\&3\ \ #\&4 
  213. X.IP
  214. Xthen parameter #\&3 could be used to extract the procedure name (in
  215. Xthis case, "main").  If #\&3 appears in an output format, it will be
  216. Xreplaced by this value.
  217. X.IP "#$"
  218. XThis parameter is used to select the last non-blank parameter on the
  219. Xmatched line.  For example, if the matched line were:
  220. X.sp
  221. X\ \ \ \ \From garyp@cognos Tue Sep 15 15:08:23 EDT 1987
  222. X.sp
  223. Xthen "#$" would select "1987", the last token on the line.
  224. X.SH FORMAT SPEC's
  225. X.LP
  226. XSubstitution parameters can be followed by an optional 
  227. X.I printf 
  228. Xstyle format specification.  They can be used to control the width of
  229. Xthe field and formatting of a numeric token into a string.
  230. X.LP
  231. XIf a numeric format like %d is used, the token is assumed to contain
  232. Xan integer value and the function \fIatoi\fR is used to convert it.
  233. XThe resulting value is then formatted with \fIsprintf\fR using the
  234. Xformat code.
  235. X.LP
  236. XFor example, the output format zzz.#\&n%02d will produce files of the
  237. Xform zzz.01, zzz.02, zzz.03, etc.  The #\&n is replaced with the current
  238. Xcount value, which is then formatted by %02d (signed decimal integer,
  239. X2 digits wide, print leading zeros).
  240. X.LP
  241. XFloating point format codes ("f", "e" and "g") are not allowed, nor is the
  242. Xsingle-character code "c".
  243. X.LP
  244. XThere is a special format code (not one of the usual \fIprintf\fR set)
  245. Xfor conversion of 3 character month names to numbers.  The code "m"
  246. Xcan be used to convert "Jan", "Feb", ...  "Dec" to the numbers 1, 2,
  247. X\&... 12.  The numeric month value is then formatted as if the format
  248. Xstring were of the %d type.  For example, the code %02m will result in
  249. Xthe month name token "Aug" being transformed into the string "08".
  250. X.SH DEFAULT FORMATS
  251. XIf no output formats are supplied, the following default format is used:
  252. X.sp .5v
  253. X\ \ \ \ #\&f:#\&n%03d
  254. X.sp .5v
  255. XThis results in files having names like \fIfilename\fR:001, 
  256. X\fIfilename\fR:002, \fIfilename\fR:003 and so on.  
  257. XThe default format was chosen because the resulting files are listed 
  258. Xin numerical order by
  259. X.I ls
  260. Xor by
  261. X.I echo *
  262. Xwhich is sometimes useful.
  263. X.LP
  264. XIf the \fB-m\fR option is used, the default output format is:
  265. X.sp .5v
  266. X\ \ \ \ #\&f:#\&$-#\&4%02m-#\&5%02d.#\&6
  267. X.sp .5v
  268. Xwhich results in files have names like \fIfilename\fR:1987-01-25.08:15:30.
  269. XThe date and time are taken from the "From" line of each message in
  270. Xthe mailbox.  
  271. X.LP
  272. XYou can take advantage of this naming convention to reassemble the
  273. Xmessages in chronological order by \fIcat\fR'ing the files back
  274. Xtogether again using the command:
  275. X.sp
  276. X\ \ \ \ cat \fIfilename\fR:*\ \  >\fImailbox.new\fR
  277. X.SH SPECIAL CONVENTIONS
  278. X.LP
  279. XNormally, \fIslice\fR opens each output file for "write".  By
  280. Xprepending an output format string with "+" (a plus sign) the
  281. Xdesignated output file will be opened for "append".  Thus, a filename
  282. Xcan appear more than once.
  283. X.LP
  284. XIf a format string is simply a "+", then \fIstdout\fR is
  285. Xused.  This is handy for piping one of the pieces to another
  286. Xcommand.  
  287. X.LP
  288. XThe special format string "@" is used as an abbreviation for
  289. X/dev/null.  This is handy for throwing away slices.
  290. X.LP
  291. X.SH EXAMPLES
  292. XSplit up a mail folder into a series of files, one for each message,
  293. Xnamed after the folder and date/time of each message.
  294. X.sp
  295. X    slice -m -f FOLDER
  296. X.sp
  297. XPipe a news article containing a shell script to slice, saving
  298. Xeverything before the line "#!/bin/sh" line to a file named HEADER and
  299. Xpiping the script portion to \fIsh\fR to unshar it:
  300. X.sp
  301. X    | slice -s -r HEADER +\ \ | sh
  302. X.sp
  303. XSplit \fIstdin\fR at every line of dashes into the files 0, 1, 2, etc.:
  304. X.sp
  305. X    cat anyfile | slice -i0\ \  "^--* *$"\ \  #\&n
  306. X.sp
  307. XPipe the middle portion of a file to sh, keeping the head and tail in a
  308. Xfile called README.  Exclude the cut lines:
  309. X.sp
  310. X    slice -f myfile -x -r README "^--* [Cc]ut "\ \ +\ \ +README\ \ | sh
  311. X.sp
  312. XKeep the middle portion of a file, discarding the head and tail:
  313. X.sp
  314. X    slice -f myfile -r @ "^--* [Cc]ut "\ \ middle @
  315. X.sp
  316. XSplit a C program which has an identifying comment before each
  317. Xprocedure into separate files for each procedure (named after the
  318. Xprocedure): 
  319. X.sp
  320. X    slice -f program.c "/[*] *Procedure: *.* *[*]/"\ \  #\&3.c
  321. X.sp
  322. X.SH BUGS
  323. X.IP a) 4
  324. XWatch out for filename expansion by the shell.  This could cause
  325. X\fIslice\fR to interpret the extra filenames as output formats causing
  326. Xfiles to be overwritten.  To be safe, do "set noclobber" in csh.
  327. X.IP b) 4
  328. XWhen the -a option is used and an output format contains token
  329. Xsubstitutions (#\&1, #\&2, etc.) the wrong filenames are generated.  To
  330. Xgenerate the correct filenames, either slice has to lookahead to find
  331. Xthe next match line or it has to direct lines for the current slice
  332. Xinto a temporary file until it finds the line matching the pattern.
  333. X.SH DIAGNOSTICS
  334. X``Internal Error'' indicates a bug in \fIslice\fR, and should be reported.
  335. XExit status 1 indicates an error parsing options \- for example, if an unknown
  336. Xflag was used.
  337. XExit status 2 indicates a meaningless combination was detected and rejected.
  338. XExit status 3 indicates a run-time problem \- for example, if a file couldn't
  339. Xbe opened.
  340. X.LP
  341. XIf a reject file is not provided, a count of rejected lines is reported.
  342. X.SH "SEE ALSO"
  343. X.I cat (1),
  344. X.I ed (1),
  345. X.I ls (1),
  346. X.I mail (1),
  347. X.I split (1),
  348. X.I mailsplit (1L),
  349. X.I atoi (3),
  350. X.I printf (3).
  351. SHAR_EOF
  352. if test 10249 -ne "`wc -c 'slice.1'`"
  353. then
  354.     echo shar: error transmitting "'slice.1'" '(should have been 10249 characters)'
  355. fi
  356. echo shar: extracting "'Makefile'" '(1301 characters)'
  357. if test -f 'Makefile'
  358. then
  359.     echo shar: over-writing existing file "'Makefile'"
  360. fi
  361. sed 's/^X//' << \SHAR_EOF > 'Makefile'
  362. X# Makefile for slice
  363. X#
  364. X# Originally contributed at mailsplit, written by:
  365. X#   R E Quin, October 1986 University of Warwick (UK) Computer Science
  366. X#   warwick!req     +44 203 523193
  367. X#
  368. X# Modified and recontributed by:
  369. X#   Gary Puckering        3755 Riverside Dr.
  370. X#   Cognos Incorporated   Ottawa, Ontario
  371. X#   (613) 738-1440        CANADA  K1G 3N3
  372. X#
  373. X# This makefile is intended for the sys5 Augmented make.
  374. X# 
  375. XMAKE=make 
  376. XCLEAN=clean 
  377. XCC=cc 
  378. X
  379. X# R is the root of the filesystem -- i.e. where to install things.
  380. X# PROG is what to make; BINDIR is where to put it.
  381. X# The binaries are installed in $R/$(BINDIR).
  382. XR=/usr/local
  383. XBINDIR=$R/bin
  384. XMANDIR=$R/man/man1
  385. XPROG=slice 
  386. XMANPAGE=slice.1
  387. X
  388. X# HACKS are for -DBUGFIX style things.
  389. XHACKS=
  390. X
  391. X# For BSD 4.2 or 4.3
  392. XUSG=
  393. X
  394. X# For USG System V version
  395. X# USG=-DUSG -lPW
  396. X
  397. XCFLAGS=-O $(HACKS) $(USG)
  398. X
  399. X
  400. X# "make install " does a $(MAKE) $(CLEAN) at the end, so you can say
  401. X# CLEAN=  make -e install
  402. X# if you don't want to remove the garbage at the end, for example.
  403. X# This is useful primarily for testing the install: entry!
  404. X
  405. Xall: $(PROG)
  406. Xslice: opts.h slice.o
  407. X    $(CC) $(CFLAGS) -o $(PROG) slice.o
  408. Xinstall: slice slice.1
  409. X    chmod a+x $(PROG)
  410. X    /bin/cp $(PROG) $(BINDIR)
  411. X    chmod a+r $(MANPAGE)
  412. X    /bin/cp $(MANPAGE) $(MANDIR)
  413. X    $(MAKE) $(CLEAN)
  414. Xclean: 
  415. X    rm -rf core *.o $(PROG) a.out
  416. SHAR_EOF
  417. if test 1301 -ne "`wc -c 'Makefile'`"
  418. then
  419.     echo shar: error transmitting "'Makefile'" '(should have been 1301 characters)'
  420. fi
  421. echo shar: extracting "'opts.h'" '(1008 characters)'
  422. if test -f 'opts.h'
  423. then
  424.     echo shar: over-writing existing file "'opts.h'"
  425. fi
  426. sed 's/^X//' << \SHAR_EOF > 'opts.h'
  427. X
  428. X#define FALSE 0
  429. X#define TRUE 1
  430. Xtypedef int bool;
  431. X
  432. X#define EXIT_SYNTAX 1    /* syntax error parsing commandline options */
  433. X#define EXIT_SEMANT 2    /* options are correct but meaningless */
  434. X#define EXIT_RUNERR 3    /* error opening a file, for example */
  435. X#define EXIT_INTERN 4    /* internal error -- bug!! */
  436. X
  437. X#define nextstr(s,count,array,failure)    \
  438. X    {if (((count)<2) && !((array)[0][1])) {failure;}\
  439. X    else {if ((array)[0][1]) { s = &((array)[0][1]); } \
  440. X          else {s = array[1]; --count; array++;}}}
  441. X
  442. X#define DFLTNAME "slice"    /* input filename (for stdin) */
  443. X#define DFLTREJECT "/dev/null" /* default reject file */
  444. X#define BUFLEN BUFSIZ    /* the maximum length of an input line (incl. "\n\0") */
  445. X#define MAXFILENAMELEN BUFSIZ    /* longer than the longest possible file name */
  446. X#define DFLTOUTNAME    "#f:#n%03d"    /* o/p file name format */
  447. X#define MBOXFORMAT  "+#f:#$-#4%02m-#5%02d.#6"    /* default for mailbox */
  448. X#define MAXPARM 9                /* maximum parm index value */
  449. X#define PARMESCAPE '#'            /* parameter escape character */
  450. SHAR_EOF
  451. if test 1008 -ne "`wc -c 'opts.h'`"
  452. then
  453.     echo shar: error transmitting "'opts.h'" '(should have been 1008 characters)'
  454. fi
  455. echo shar: extracting "'slice.c'" '(15915 characters)'
  456. if test -f 'slice.c'
  457. then
  458.     echo shar: over-writing existing file "'slice.c'"
  459. fi
  460. sed 's/^X//' << \SHAR_EOF > 'slice.c'
  461. X/*
  462. X *    Program name:  slice
  463. X *
  464. X *    Copyright (c) 1987 by Gary Puckering
  465. X *
  466. X *    This program may be freely used and/or modified, 
  467. X *    with the following provisions:
  468. X *    1. This notice and the above copyright notice must remain intact.
  469. X *    2. Neither this program, nor any modification of it,
  470. X *       may be sold for profit without written consent of the author.
  471. X *
  472. X *    -----------------------------------------------------------------
  473. X *
  474. X *    This program allows you to cut a file into pieces, either at every
  475. X *  n lines (like fsplit) or based on a pattern match.  Slices are sent
  476. X *  to output files, which are named by providing a format string.  A
  477. X *  file name may be a constant, or may contain substitution parameters,
  478. X *  such as #f for the input file name or #1, #2, ... #9 for tokens
  479. X *  1 through 9 on the line matching the pattern.
  480. X *
  481. X *    -----------------------------------------------------------------
  482. X */
  483. X
  484. Xchar version[] = "@(#)slice.c    2.4";
  485. X
  486. X#include <stdio.h>
  487. X#include <ctype.h>
  488. X#include <string.h>
  489. X#include <sys/file.h>
  490. X
  491. X#include "opts.h"                /* defines nextstr() etc */
  492. X
  493. Xbool exclude = FALSE;            /* exclude matched line from o/p files */
  494. Xbool split_after = FALSE;        /* split after matched line */
  495. Xbool m_flag = FALSE;            /* was -m option used */
  496. X
  497. XFILE *output = (FILE *) NULL;    /* fd of current output file */
  498. XFILE *rejectfd = (FILE *) NULL;    /* fd of reject file */
  499. X
  500. Xint  n_format;                    /* number of format strings */
  501. Xint  filenumber = 0;            /* #n substitution for each file */
  502. Xint  every_n_lines = 0;            /* split every n lines */
  503. Xint rejectcnt = 0;                /* count of rejected lines */
  504. X
  505. Xchar inbuffer[BUFLEN];            /* input buffer */
  506. Xchar *progname = "slice";        /* for error messages */
  507. Xchar *pattern = (char *) NULL;    /* reg expr used to split file */
  508. Xchar **format;                    /* ptr for format strings */
  509. Xchar *defaultfmt[] = {DFLTOUTNAME};    /* default format string */
  510. Xchar *mboxformat[] = {MBOXFORMAT};    /* default format for mailboxes */
  511. Xchar parmbuf[BUFLEN];            /* parameter buffer */
  512. Xchar *parm[MAXPARM+1];            /* array of pointers to parms */
  513. Xchar nullstring[1] = {""};        /* a null string */
  514. Xchar *infile = (char *) NULL;    /* input file name */
  515. Xchar rejectfile[MAXFILENAMELEN+2] = {DFLTREJECT}; /* reject file name */
  516. X
  517. X/* forward declarations */
  518. XFILE * openfile();
  519. XFILE * mkreject();
  520. Xchar * mkname();
  521. Xchar * rmpath();
  522. Xchar   getfmt();
  523. X
  524. X
  525. Xmain(argc, argv)
  526. X    char *argv[];
  527. X{
  528. X    /* split files at points that match a given pattern */
  529. X
  530. X    /* initialise things */
  531. X    char *buffer;
  532. X    int i;
  533. X    int getnum();        /* does more checking than atoi */
  534. X    char *rmpath();    /* removes leading pathname from a filename */
  535. X
  536. X    for (i=0; i<=MAXPARM; i++) parm[i] = nullstring;
  537. X
  538. X    /* now remove possible leading pathname
  539. X     * (e.g. /usr/bin/slice is to report it's errors as slice
  540. X    */
  541. X    progname = rmpath(argv[0]);
  542. X
  543. X
  544. X    while (--argc) {
  545. X      if (**++argv == '-') {
  546. X        switch(*++*argv) {
  547. X            case 'a': {                /* split after pattern */
  548. X                split_after = TRUE;
  549. X                break;
  550. X            }
  551. X            case 'e': {                /* pattern (expression) */
  552. X                ++argv; argc--;
  553. X                if (argc==0 || !**argv) {
  554. X                    error("Pattern after -e missing or null\n");
  555. X                    usage(1);
  556. X                }
  557. X                pattern = *argv;
  558. X                break;
  559. X            }
  560. X            case 'm': {                /* mailbox pattern */
  561. X                pattern = "^From ";
  562. X                m_flag = TRUE;
  563. X                break; 
  564. X            }
  565. X            case 's': {                /* shell pattern */
  566. X                pattern = "^#! *\/bin\/sh";
  567. X                break; 
  568. X            }
  569. X            case 'n': {                /* -n n_lines -- split every n lines */
  570. X                nextstr(buffer,argc,argv,usage(2));
  571. X                every_n_lines = getnum(buffer);
  572. X                if (every_n_lines <= 0) {
  573. X                    error("-n: number must be at least 1\n");
  574. X                    exit(EXIT_SYNTAX);
  575. X                }
  576. X                break;
  577. X            } 
  578. X            case 'f': {
  579. X                ++argv; argc--;
  580. X                if (argc==0 || !**argv) {
  581. X                    error("Filename after -f missing or null\n");
  582. X                    usage(1);
  583. X                }
  584. X                infile = *argv;
  585. X                break;
  586. X            }                
  587. X            case 'r': {
  588. X                ++argv; argc--;
  589. X                if (argc==0 ||!**argv) {
  590. X                    error("Filename after -r missing or null\n");
  591. X                    usage(1);
  592. X                }
  593. X                strcpy(rejectfile,*argv);
  594. X                break;
  595. X            }
  596. X            case 'i': {    /* -i initial_number */
  597. X                nextstr(buffer,argc,argv,usage(2));
  598. X                filenumber = getnum(buffer);
  599. X                if (filenumber < 0) {
  600. X                    error("-i must be followed by a positive number\n");
  601. X                    exit(EXIT_SYNTAX);
  602. X                 }
  603. X                filenumber--;    /* needs to be one less to start with */
  604. X                break;
  605. X            }
  606. X            case 'x': { /* exclude matched lines */
  607. X                exclude = TRUE;
  608. X                break;
  609. X            }
  610. X            default: {
  611. X                error("Unknown flag -%c\n", **argv);
  612. X                usage(1);
  613. X            }
  614. X        }            /* end switch */
  615. X      } else {    
  616. X        if (!pattern) pattern = *argv;    /* first non-flag is pattern */
  617. X        else break;                        /* break while loop */
  618. X      }            /* end if */
  619. X     }        /* end while */
  620. X
  621. X     if (!argc) {
  622. X        if (m_flag) {
  623. X            format = mboxformat;
  624. X        } else {
  625. X            format = defaultfmt;
  626. X        }
  627. X        n_format = 1; 
  628. X     } else {
  629. X        format = argv;
  630. X        n_format = argc;
  631. X     }
  632. X
  633. X#ifdef DEBUG
  634. X    printf("argc=%d\n",argc);
  635. X    printf("format='%s'\n",*format);
  636. X    printf("pattern='%s'\n",pattern);
  637. X#endif
  638. X
  639. X     if (!infile) split(stdin, DFLTNAME, pattern);
  640. X     else        fsplit(infile, pattern);
  641. X
  642. X     exit(0);
  643. X}
  644. X
  645. X
  646. X/* split a file that hasn't been opened yet */
  647. X
  648. Xfsplit(name, pat)
  649. X     char *name;
  650. X     char *pat;
  651. X{
  652. X     FILE *fd;
  653. X
  654. X     if (!name || !*name) {
  655. X      error("Can't split a file with an empty name\n");
  656. X      usage(2);
  657. X     }
  658. X
  659. X     if ( (fd = fopen(name, "r")) == NULL) {
  660. X      error("Can't open %s\n", name);
  661. X      return;
  662. X     }
  663. X
  664. X     (void) split(fd, name, pat);
  665. X
  666. X     if (fclose(fd) == EOF) {    /* something's gone wrong */
  667. X      error("Can't close %s -- giving up\n", name);
  668. X      exit(EXIT_RUNERR);
  669. X     }
  670. X}
  671. X
  672. X
  673. X/* Split a file that's already been opened */
  674. X
  675. Xsplit(input, name, pattern)
  676. X     FILE *input;        /* fd of input file */
  677. X     char *name;        /* input filename */
  678. X     char *pattern;        /* pattern used to split file */
  679. X{
  680. X
  681. X#ifndef USG
  682. X     extern char *re_comp();     /* compile string into automaton */
  683. X     extern int   re_exec();    /* try to match string */
  684. X     int reg_status = 0;    /* regular expression status */
  685. X     char *errmessage;
  686. X#define REMATCH 1
  687. X#define RENOMATCH 0
  688. X#define REFAULT -1
  689. X#define match(expr) ((expr) == REMATCH)
  690. X#else
  691. X     extern char *regcmp();    /* compile string into automaton */
  692. X     extern char *regex();    /* match string with automaton */
  693. X     char *reg_status;        /* regular expression status */
  694. X     char *rex;
  695. X#define match(expr) ((expr) != NULL)
  696. X#endif
  697. X
  698. X     char *fname;
  699. X     int line = 0;
  700. X
  701. X     rejectcnt = 0;
  702. X
  703. X     if (split_after && exclude) {
  704. X      error("Can't specify both -a and -x\n");
  705. X      usage(2);
  706. X     }
  707. X
  708. X     if (every_n_lines && exclude) {
  709. X      error("Can't specify both -n and -x\n");
  710. X      usage(2);
  711. X     }
  712. X
  713. X     if (every_n_lines && split_after) {
  714. X      error("Can't specify both -n and -a\n");
  715. X      usage(2);
  716. X     }
  717. X
  718. X     if (every_n_lines && pattern) {
  719. X      error("Can't specify both -n and pattern\n");
  720. X      usage(2);
  721. X     }
  722. X
  723. X     if (!every_n_lines && (!pattern || !*pattern)) {
  724. X      error("Can't match an empty pattern\n");
  725. X      usage(2);
  726. X     }
  727. X
  728. X#ifndef USG
  729. X     if (!every_n_lines && (errmessage = re_comp(pattern)) != NULL) {
  730. X      error("Error in pattern <%s>: %s\n", pattern, errmessage);
  731. X      exit(EXIT_RUNERR);
  732. X     }
  733. X     /* errmessage is NULL here */
  734. X#else
  735. X     if (!every_n_lines && (rex = regcmp(pattern,(char *)0)) == NULL) {
  736. X      error("Erron in pattern <%s>\n", pattern);
  737. X      exit(EXIT_RUNERR);
  738. X     }
  739. X     /* rex is pointer to compiled expression.... */
  740. X#endif
  741. X
  742. X    /* if split after mode, open file at start */
  743. X    if (split_after) {
  744. X        fname = mkname(name);
  745. X        output = openfile(fname);
  746. X}
  747. X    
  748. X     /* the -2 to fgets is because of the null and \n appended */
  749. X     while (fgets(inbuffer, BUFLEN - 2, input) != NULL) {
  750. X
  751. X      if ((every_n_lines > 0 && (++line == every_n_lines)) ||     /* nth line */
  752. X         (!every_n_lines &&
  753. X#ifndef USG
  754. X         ( match(reg_status = re_exec(inbuffer) ) ) ) ) {         /* matches pat */
  755. X#else
  756. X         ( match(reg_status = regex(rex, inbuffer) ) ) ) ) {         /* matches pat */
  757. X#endif
  758. X
  759. X            if (split_after) putbuff(inbuffer,output);
  760. X
  761. X            if (!every_n_lines) get_parms(inbuffer);
  762. X            
  763. X            /* close the current file */
  764. X            if (output && output != stderr && 
  765. X                          output != stdout &&
  766. X                          output != rejectfd) {
  767. X                if (fclose(output) == EOF) {
  768. X                    error("Can't close output file\n");
  769. X                    exit(EXIT_RUNERR);
  770. X                }
  771. X            }
  772. X
  773. X            fname = mkname(name);
  774. X
  775. X            if (*fname) {
  776. X                /* open a new file */
  777. X                output = openfile(fname);
  778. X            } else {
  779. X                /* no filename to open, so use reject file */
  780. X                error("Insufficient formats -- remainder rejected\n");
  781. X                output = (FILE *) NULL;
  782. X            }
  783. X
  784. X            line = 0;  /* reset input line count */
  785. X
  786. X            /* if matched lines are excluded, skip the putbuff */
  787. X            if (exclude && match(reg_status)) continue;
  788. X
  789. X            /* if file is to be split after pattern, put already done */
  790. X            if (split_after && match(reg_status)) continue;
  791. X#ifndef USG
  792. X      } else {
  793. X              if (reg_status == REFAULT) {    /* the re_exec failed */
  794. X                error("Internal error trying to match <%s> to <%s>\n",pattern, inbuffer);
  795. X                exit(EXIT_INTERN);
  796. X            }
  797. X#endif
  798. X      }  /* end match pattern test  */
  799. X
  800. X      putbuff(inbuffer, output);        /* now put line out */
  801. X
  802. X      }  /* end while */
  803. X
  804. X      if (rejectcnt && strcmp(rejectfile,"/dev/null")==0) {
  805. X          error("%d lines rejected to /dev/null\n",rejectcnt);
  806. X      }
  807. X      
  808. X      return (filenumber == -1);    /* exit status for main */
  809. X}
  810. X
  811. X
  812. X/* Make a reject file */
  813. X
  814. XFILE *
  815. Xmkreject()
  816. X{
  817. X    if (rejectfd) return(rejectfd);    /* if there's already one, don't bother */
  818. X
  819. X    if ( strcmp(rejectfile,"stderr")==0 ) 
  820. X            rejectfd = stderr;
  821. X    else    rejectfd = openfile(rejectfile);
  822. X
  823. X    if (!rejectfd) {
  824. X        error("Cannot open reject file %s\n",rejectfile);
  825. X        exit(EXIT_RUNERR);
  826. X    }
  827. X
  828. X    return (rejectfd);
  829. X}
  830. X
  831. X
  832. X/* Open an output file (or the reject file) */
  833. X
  834. X/* If filename starts with '+' open for append */
  835. X/* If filename is '@' use /dev/null */
  836. X
  837. XFILE *
  838. Xopenfile(fname)
  839. X    char *fname;                /* file to be opened */
  840. X{
  841. X    FILE *fd = (FILE *) NULL;
  842. X    bool exists = FALSE;
  843. X
  844. X    switch (fname[0]) {
  845. X        case '+': {
  846. X            fname++;
  847. X            /* check for output file = input file */
  848. X            if (infile && (strcmp(fname,infile)==0) ) {
  849. X                error("Output file %s same as input file -- slice rejected\n",fname);
  850. X                break;
  851. X            }
  852. X            if (fname[0]==NULL) {
  853. X                fd = stdout;
  854. X                break;
  855. X            } else {
  856. X                if ((fd = fopen(fname, "a")) == NULL) {
  857. X                    error("Can't open output file %s for append -- slice rejected\n", fname[1]);
  858. X                    break;
  859. X                }
  860. X            }
  861. X            break;
  862. X        }
  863. X        case '@': {
  864. X            fname++;
  865. X            if (fname[0]==NULL) {
  866. X                if ((fd = fopen("/dev/null", "w")) == NULL) {
  867. X                    error("Can't open output file /dev/null -- slice rejected\n");
  868. X                    break;
  869. X                }
  870. X                break;
  871. X            } else {
  872. X                fname--;
  873. X                /* fall through to process as normal filename */
  874. X            }
  875. X        }
  876. X        default: {
  877. X            if (access(fname,F_OK)==0) exists = TRUE;
  878. X            if ((fd = fopen(fname, "w")) == NULL) {
  879. X                error("Can't open output file %s for write -- slice rejected\n", fname);
  880. X                break;
  881. X            }
  882. X            if (exists && strcmp(fname,"/dev/null")!=0) {
  883. X                error("File %s overwritten\n",fname);
  884. X            }
  885. X        }
  886. X    }
  887. X    return (fd);
  888. X}
  889. X
  890. X
  891. X/* Make a new file name using a format string */
  892. X
  893. Xchar *
  894. Xmkname(name)
  895. X    char *name;        /* file name for #f substitution */
  896. X{
  897. X    static char fnambuf[MAXFILENAMELEN + 2]; /* +1 for null, +1 for overflow */
  898. X    static char lastname[MAXFILENAMELEN + 2];
  899. X
  900. X    int i, j;
  901. X    char *p, *q, *fn;
  902. X    char fmtcode;
  903. X    char fmt[MAXFILENAMELEN];
  904. X    char tempbuf[MAXFILENAMELEN];
  905. X
  906. X  do_format:
  907. X    for (p=(*format), q=fnambuf; *p; p++) {
  908. X        if (*p != PARMESCAPE) { 
  909. X            *q = *p; 
  910. X            q++;
  911. X        } else {
  912. X            *q = NULL;
  913. X            switch (*++p) {
  914. X                case PARMESCAPE: {
  915. X                    *q = PARMESCAPE;
  916. X                    q++;
  917. X                    break;
  918. X                }
  919. X                case 'f': {
  920. X                    fn = rmpath(name);
  921. X                    strcat(q,fn);
  922. X                    q += strlen(fn);
  923. X                    break;
  924. X                }
  925. X                case 'n': {
  926. X                    ++filenumber;
  927. X                    if (*(p+1) == '%') {
  928. X                        p++;
  929. X                        fmtcode = getfmt(fmt,p);
  930. X                        p += strlen(fmt) - 1;
  931. X                        sprintf(tempbuf,fmt,filenumber);
  932. X                    } else {
  933. X                        sprintf(tempbuf,"%d",filenumber);
  934. X                    }
  935. X                    strcat(q,tempbuf);
  936. X                    q += strlen(tempbuf);
  937. X                    break;
  938. X                }
  939. X                case '1':
  940. X                case '2':
  941. X                case '3':
  942. X                case '4':
  943. X                case '5':
  944. X                case '6':
  945. X                case '7':
  946. X                case '8':
  947. X                case '9': 
  948. X                case '$': {
  949. X                    if (*p == '$') {
  950. X                        i = lastparm();
  951. X                    } else {
  952. X                        i = (*p) - '1';
  953. X                    }
  954. X                    if (*(p+1) == '%') {
  955. X                        p++;
  956. X                        fmtcode = getfmt(fmt,p);
  957. X                        p += strlen(fmt) - 1;
  958. X                        if (fmtcode != 's') {
  959. X                            if (fmtcode == 'm') {
  960. X                                j = mtoi(parm[i]);
  961. X                            } else {
  962. X                                j = atoi(parm[i]);
  963. X                            }
  964. X                            sprintf(tempbuf,fmt,j);
  965. X                        } else {
  966. X                            sprintf(tempbuf,fmt,parm[i]);
  967. X                        }
  968. X                    } else {
  969. X                        strcpy(tempbuf,parm[i]);
  970. X                    }
  971. X                    strcat(q,tempbuf);
  972. X                    q += strlen(tempbuf);
  973. X                    break;
  974. X                }
  975. X                default: {
  976. X                    error("Invalid substitution #%c in format '%s'\n",*p,*format);
  977. X                    exit(EXIT_RUNERR);
  978. X                }
  979. X            }    /* end switch */
  980. X        }    /* end if-else */
  981. X    }    /* end for */
  982. X    
  983. X    *q = NULL;
  984. X
  985. X    /* if name is same, try next format */
  986. X    if (strcmp(fnambuf,lastname)==0) {
  987. X        if (n_format>1) {    /* must be a format left to try */
  988. X            format++;
  989. X            --n_format;
  990. X            filenumber=0;
  991. X            lastname[0] = NULL;
  992. X            goto do_format;
  993. X        } else {            /* we've run out of formats */
  994. X            fnambuf[0] = NULL;            
  995. X        }
  996. X    }
  997. X
  998. X    if (fnambuf[0]) strcpy(lastname,fnambuf);
  999. X
  1000. X    return(fnambuf);
  1001. X
  1002. X} /* end routine */
  1003. X
  1004. X
  1005. X
  1006. X/* Get a printf-style format string from within an output format */
  1007. X
  1008. Xchar
  1009. Xgetfmt(fmt,p)        /* returns last character of string (format code) */
  1010. X    char *fmt;        /* target of format string */
  1011. X    char *p;        /* p should point to the '%' starting the format */
  1012. X{
  1013. X    char *q;
  1014. X    char fmtcode;
  1015. X
  1016. X    if (*p != '%') {
  1017. X        error("Internal error -- getfmt called when not pointing at %");
  1018. X        exit(EXIT_RUNERR);
  1019. X    }
  1020. X    q = strpbrk(p,"mduxhocsfeg");  /* 'm' is a special extension */
  1021. X    
  1022. X    if (!q) {
  1023. X        error("Can't find end of format spec");
  1024. X        exit(EXIT_RUNERR);
  1025. X    }
  1026. X
  1027. X    fmtcode = *q;
  1028. X    strncpy(fmt,p,(q-p+1));
  1029. X
  1030. X    switch (*q) {
  1031. X        case 'h':
  1032. X        case 'c':
  1033. X        case 'f':
  1034. X        case 'e':
  1035. X        case 'g': {
  1036. X            error("Format '%s' is not supported\n",fmt);
  1037. X            exit(EXIT_RUNERR);
  1038. X        }
  1039. X        case 'm': {
  1040. X            fmt[strlen(fmt) - 1] = 'd';    /* change to d */
  1041. X        }
  1042. X    }
  1043. X
  1044. X    return(fmtcode);
  1045. X}
  1046. X
  1047. X
  1048. X
  1049. X/* Write an input line to the output file */
  1050. X/* If the output file isn't open, write it to the reject file */
  1051. X
  1052. Xputbuff(buffer,fd)
  1053. X    char *buffer;
  1054. X    FILE *fd;
  1055. X{
  1056. X    if (fd) fputs(buffer,fd);
  1057. X    else {
  1058. X        rejectcnt++;
  1059. X        fputs( buffer,mkreject() );
  1060. X    }
  1061. X}
  1062. X
  1063. X
  1064. X
  1065. X/* getnum(s) returns the value of the unsigned int in s.  If there's any
  1066. X * trailing garbage, or the number isn't +ve, we return -1
  1067. X */
  1068. X
  1069. Xgetnum(s)
  1070. X     char *s;
  1071. X{
  1072. X     register char *p;
  1073. X
  1074. X     for (p = s; *p; p++) {
  1075. X      if (!isdigit(*p)) {
  1076. X           return -1;
  1077. X      }
  1078. X     }
  1079. X     return atoi(s);
  1080. X}
  1081. X
  1082. X
  1083. X
  1084. X/* Remove the leading pathname from a filename */
  1085. X
  1086. Xchar *
  1087. Xrmpath(fullname)
  1088. X    char *fullname;
  1089. X{
  1090. X    register char *p;
  1091. X    char *q = (char *) NULL;
  1092. X
  1093. X    for (p = fullname; p && *p; p++) {
  1094. X         if (*p == '/')
  1095. X          q = ++p;
  1096. X    }
  1097. X    if (q && *q) {
  1098. X         return(q);
  1099. X    }
  1100. X    return(fullname);
  1101. X}
  1102. X
  1103. X
  1104. X
  1105. X/* Get tokens (parameters) from matched input lines */
  1106. X
  1107. Xget_parms(buffer)
  1108. X    char *buffer;
  1109. X{
  1110. X    int  i,l;
  1111. X
  1112. X    strcpy(parmbuf,buffer);
  1113. X    l = strlen(parmbuf);
  1114. X    if(parmbuf[l-1]=='\n') parmbuf[l-1]=NULL;
  1115. X
  1116. X    parm[0]=strtok(parmbuf," ");
  1117. X
  1118. X    for (i=1; i<=MAXPARM; i++) {
  1119. X        parm[i]=strtok(NULL," ");
  1120. X    }
  1121. X
  1122. X    for (i=0; i<=MAXPARM; i++) {
  1123. X        if (!parm[i]) parm[i] = nullstring;
  1124. X    }
  1125. X}
  1126. X
  1127. X
  1128. X
  1129. X/* Find last non-null parameter */
  1130. X
  1131. Xlastparm()
  1132. X{
  1133. X    int i;
  1134. X
  1135. X    for (i=MAXPARM; i>0; i--) {
  1136. X        if (parm[i] && *parm[i]) return(i);
  1137. X    }
  1138. X    return(0);
  1139. X}
  1140. X
  1141. X
  1142. X
  1143. X/* Convert three character month name to integer */
  1144. X
  1145. Xmtoi(monthname)
  1146. X    char *monthname;
  1147. X{
  1148. X    static char *months[] = { 
  1149. X        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  1150. X        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  1151. X        };
  1152. X
  1153. X    int i;
  1154. X
  1155. X    for (i=1; i<=12; i++) {
  1156. X        if (strcmp(monthname,months[i-1])==0) return(i);
  1157. X    }
  1158. X
  1159. X    error("Invalid month '%s' found -- zero used",monthname);
  1160. X    i=0;
  1161. X    return(i);
  1162. X}
  1163. X
  1164. X
  1165. Xerror(fmt, a1, a2, a3, a4)
  1166. X     char *fmt;
  1167. X{
  1168. X     fputs(progname, stderr);
  1169. X     fputs(": ", stderr);
  1170. X     fprintf(stderr, fmt, a1, a2, a3, a4);
  1171. X}
  1172. X
  1173. X
  1174. Xusage(status)
  1175. X     int status;    /* exit if status != 0 */
  1176. X{
  1177. X     fprintf(stderr,"Usage: %s [-f filename] [-a|-x] [-i<n>] [-w] [-m|-s|-n<n>] [-r file] [-e expression | expression] [format...]\n", progname);
  1178. X     if (status)
  1179. X      exit(status);
  1180. X}
  1181. X
  1182. SHAR_EOF
  1183. if test 15915 -ne "`wc -c 'slice.c'`"
  1184. then
  1185.     echo shar: error transmitting "'slice.c'" '(should have been 15915 characters)'
  1186. fi
  1187. echo shar: extracting "'CHANGES'" '(2731 characters)'
  1188. if test -f 'CHANGES'
  1189. then
  1190.     echo shar: over-writing existing file "'CHANGES'"
  1191. fi
  1192. sed 's/^X//' << \SHAR_EOF > 'CHANGES'
  1193. X------------------------------ CHANGES ------------------------------
  1194. X
  1195. Xv2.3    87/09/20  21:36:52  garyp
  1196. X        Make default reject file /dev/null.  Report count of lines
  1197. X        rejected, unless reject file specified.
  1198. X
  1199. Xv2.2    87/09/15  17:33:33  garyp
  1200. X        Fix bug that caused dump when parm was null.  Changed mkname so
  1201. X        that the path is removed from the input filename.  Added #$
  1202. X        substitution parameter, to correctly split mailboxes.  Changed
  1203. X        the default pattern for -m to use #$ instead of #7.
  1204. X
  1205. Xv2.1    87/09/15  00:20:07  garyp
  1206. X        New release of slice, now supports substitution parameters.
  1207. X        Use of %s and %d replaced by #f and #n respectively.
  1208. X        Parameters #1, #2, ... #9 are replaced by tokens from the
  1209. X        matched line.  Printf-style format (e.g. %02d) may immediately
  1210. X        follow any substitution.
  1211. X
  1212. Xv1.12   87/09/10  17:05:04  garyp
  1213. X        Reject file now added.
  1214. X
  1215. Xv1.11   87/09/08  23:53:29  garyp
  1216. X        Add features "+<filename>" and "@"
  1217. X
  1218. Xv1.10   87/02/12  10:32:50  garyp
  1219. X        Minor revision to Sys V changes Added #define match() to reduce
  1220. X        number of #ifndef USG Added some comments
  1221. X
  1222. Xv1.9    87/02/09  10:10:27  garyp
  1223. X        Revisions to support System V regular expression routines.
  1224. X        Provided by Jon A. Tankersley.
  1225. X
  1226. Xv1.8    86/12/12  22:28:40  garyp
  1227. X        Add -x and -a options.  Improve semantic checking.  Fix -n
  1228. X        (wasn't working -- caused core dump).
  1229. X
  1230. Xv1.7    86/12/12  11:54:56  garyp
  1231. X        Fix problem with multiple '+' formats Switch from 'w' to 'a'
  1232. X        (append) access for output files.
  1233. X
  1234. Xv1.6    86/12/11  21:18:08  garyp
  1235. X        Cleaned up error status codes.  Allowed '+' format to designate
  1236. X        stdout.
  1237. X
  1238. Xv1.5    86/12/11  17:29:25  garyp
  1239. X        New improved mailsplit with lots of features.  Renamed slice.
  1240. X
  1241. Xv1.4    86/12/09  17:03:05  garyp
  1242. X        First parameter is now either -m, for mailbox pattern, or -s,
  1243. X        for shell pattern, or -n for line count split, or a user
  1244. X        specified pattern (not starting with '-').  These options
  1245. X        replace -p.  The default output filename is now "%s:%03d" where
  1246. X        %s is the input filename.
  1247. X
  1248. Xv1.3    86/12/09  12:57:49  garyp
  1249. X        Fixed search for %d.  All forms now allowed.  Only %s allowed,
  1250. X        though (no other form seems useful anyway).
  1251. X
  1252. Xv1.2    86/12/09  12:10:09  garyp
  1253. X        Changed to allow output pattern to contain %s to represent
  1254. X        input filename.  However, code only does exact match on %d and
  1255. X        %s.  It should do a pattern match to allow things like %6d.
  1256. X
  1257. Xv1.1    86/12/08  21:08:12  garyp
  1258. X        date and time created 86/12/08 21:08:12 by garyp
  1259. X---------------------------------------------------------------------
  1260. SHAR_EOF
  1261. if test 2731 -ne "`wc -c 'CHANGES'`"
  1262. then
  1263.     echo shar: error transmitting "'CHANGES'" '(should have been 2731 characters)'
  1264. fi
  1265. #    End of shell archive
  1266. exit 0
  1267.  
  1268.  
  1269.