Annotation of src/usr.bin/join/join.c, Revision 1.6
1.6 ! millert 1: /* $Id: join.c,v 1.5 1996/08/12 16:37:00 michaels Exp $
1.3 deraadt 2:
1.1 deraadt 3: /*-
1.4 michaels 4: * Copyright (c) 1991, 1993, 1994
5: * The Regents of the University of California. All rights reserved.
1.1 deraadt 6: *
7: * This code is derived from software contributed to Berkeley by
1.4 michaels 8: * Steve Hayman of the Computer Science Department, Indiana University,
9: * Michiro Hikida and David Goodenough.
1.1 deraadt 10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * 3. All advertising materials mentioning features or use of this software
20: * must display the following acknowledgement:
21: * This product includes software developed by the University of
22: * California, Berkeley and its contributors.
23: * 4. Neither the name of the University nor the names of its contributors
24: * may be used to endorse or promote products derived from this software
25: * without specific prior written permission.
26: *
27: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37: * SUCH DAMAGE.
38: */
39:
40: #ifndef lint
1.4 michaels 41: static char copyright[] =
42: "@(#) Copyright (c) 1991, 1993, 1994\n\
43: The Regents of the University of California. All rights reserved.\n";
1.1 deraadt 44: #endif /* not lint */
45:
46: #ifndef lint
1.4 michaels 47: /*static char sccsid[] = "@(#)join.c 8.6 (Berkeley) 5/4/95"; */
1.6 ! millert 48: static char rcsid[] = "$Id: join.c,v 1.5 1996/08/12 16:37:00 michaels Exp $";
1.1 deraadt 49: #endif /* not lint */
50:
1.4 michaels 51: #include <sys/param.h>
52:
53: #include <ctype.h>
54: #include <err.h>
55: #include <errno.h>
1.1 deraadt 56: #include <stdio.h>
57: #include <stdlib.h>
58: #include <string.h>
1.4 michaels 59: #include <unistd.h>
1.1 deraadt 60:
61: /*
62: * There's a structure per input file which encapsulates the state of the
63: * file. We repeatedly read lines from each file until we've read in all
64: * the consecutive lines from the file with a common join field. Then we
65: * compare the set of lines with an equivalent set from the other file.
66: */
67: typedef struct {
1.4 michaels 68: char *line; /* line */
69: u_long linealloc; /* line allocated count */
70: char **fields; /* line field(s) */
71: u_long fieldcnt; /* line field(s) count */
1.1 deraadt 72: u_long fieldalloc; /* line field(s) allocated count */
1.4 michaels 73: u_long cfieldc; /* current field count */
74: long fpos; /* fpos of start of field */
1.1 deraadt 75: } LINE;
76:
77: typedef struct {
1.4 michaels 78: FILE *fp; /* file descriptor */
79: u_long joinf; /* join field (-1, -2, -j) */
80: int unpair; /* output unpairable lines (-a) */
81: int number; /* 1 for file 1, 2 for file 2 */
82:
83: LINE *set; /* set of lines with same field */
84: int pushbool; /* if pushback is set */
85: u_long pushback; /* line on the stack */
86: u_long setcnt; /* set count */
87: u_long setalloc; /* set allocated count */
88: u_long setusedc; /* sets used */
1.1 deraadt 89: } INPUT;
1.5 michaels 90: INPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, },
91: input2 = { NULL, 0, 0, 2, NULL, 0, 0, 0, };
1.1 deraadt 92:
93: typedef struct {
1.4 michaels 94: u_long filenum; /* file number */
1.1 deraadt 95: u_long fieldno; /* field number */
96: } OLIST;
97: OLIST *olist; /* output field list */
98: u_long olistcnt; /* output field list count */
99: u_long olistalloc; /* output field allocated count */
100:
101: int joinout = 1; /* show lines with matched join fields (-v) */
102: int needsep; /* need separator character */
103: int spans = 1; /* span multiple delimiters (-t) */
104: char *empty; /* empty field replacement string (-e) */
105: char *tabchar = " \t"; /* delimiter characters (-t) */
106:
107: int cmp __P((LINE *, u_long, LINE *, u_long));
108: void fieldarg __P((char *));
109: void joinlines __P((INPUT *, INPUT *));
110: void obsolete __P((char **));
1.4 michaels 111: void outfield __P((LINE *, u_long, int));
1.1 deraadt 112: void outoneline __P((INPUT *, LINE *));
113: void outtwoline __P((INPUT *, LINE *, INPUT *, LINE *));
114: void slurp __P((INPUT *));
1.4 michaels 115: void slurpit __P((INPUT *));
1.1 deraadt 116: void usage __P((void));
117:
118: int
119: main(argc, argv)
120: int argc;
121: char *argv[];
122: {
1.4 michaels 123: INPUT *F1, *F2;
1.1 deraadt 124: int aflag, ch, cval, vflag;
125: char *end;
126:
127: F1 = &input1;
128: F2 = &input2;
129:
130: aflag = vflag = 0;
131: obsolete(argv);
1.6 ! millert 132: while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
1.1 deraadt 133: switch (ch) {
1.4 michaels 134: case '\01': /* See comment in obsolete(). */
1.1 deraadt 135: aflag = 1;
136: F1->unpair = F2->unpair = 1;
137: break;
138: case '1':
139: if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
1.4 michaels 140: errx(1, "-1 option field number less than 1");
1.1 deraadt 141: if (*end)
1.4 michaels 142: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 143: --F1->joinf;
144: break;
145: case '2':
146: if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
1.4 michaels 147: errx(1, "-2 option field number less than 1");
1.1 deraadt 148: if (*end)
1.4 michaels 149: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 150: --F2->joinf;
151: break;
152: case 'a':
153: aflag = 1;
154: switch(strtol(optarg, &end, 10)) {
155: case 1:
156: F1->unpair = 1;
157: break;
158: case 2:
159: F2->unpair = 1;
160: break;
161: default:
1.4 michaels 162: errx(1, "-a option file number not 1 or 2");
1.1 deraadt 163: break;
164: }
165: if (*end)
1.4 michaels 166: errx(1, "illegal file number -- %s", optarg);
1.1 deraadt 167: break;
168: case 'e':
169: empty = optarg;
170: break;
171: case 'j':
1.4 michaels 172: if ((F1->joinf = F2->joinf = strtol(optarg, &end, 10)) < 1)
173: errx(1, "-j option field number less than 1");
1.1 deraadt 174: if (*end)
1.4 michaels 175: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 176: --F1->joinf;
177: --F2->joinf;
178: break;
179: case 'o':
180: fieldarg(optarg);
181: break;
182: case 't':
183: spans = 0;
184: if (strlen(tabchar = optarg) != 1)
1.4 michaels 185: errx(1, "illegal tab character specification");
1.1 deraadt 186: break;
187: case 'v':
188: vflag = 1;
189: joinout = 0;
1.4 michaels 190: switch (strtol(optarg, &end, 10)) {
1.1 deraadt 191: case 1:
192: F1->unpair = 1;
193: break;
194: case 2:
195: F2->unpair = 1;
196: break;
197: default:
1.4 michaels 198: errx(1, "-v option file number not 1 or 2");
1.1 deraadt 199: break;
200: }
201: if (*end)
1.4 michaels 202: errx(1, "illegal file number -- %s", optarg);
1.1 deraadt 203: break;
204: case '?':
205: default:
206: usage();
207: }
208: }
209: argc -= optind;
210: argv += optind;
211:
212: if (aflag && vflag)
1.4 michaels 213: errx(1, "the -a and -v options are mutually exclusive");
1.1 deraadt 214:
215: if (argc != 2)
216: usage();
217:
218: /* Open the files; "-" means stdin. */
219: if (!strcmp(*argv, "-"))
220: F1->fp = stdin;
221: else if ((F1->fp = fopen(*argv, "r")) == NULL)
1.4 michaels 222: err(1, "%s", *argv);
1.1 deraadt 223: ++argv;
224: if (!strcmp(*argv, "-"))
225: F2->fp = stdin;
226: else if ((F2->fp = fopen(*argv, "r")) == NULL)
1.4 michaels 227: err(1, "%s", *argv);
1.1 deraadt 228: if (F1->fp == stdin && F2->fp == stdin)
1.4 michaels 229: errx(1, "only one input file may be stdin");
1.1 deraadt 230:
1.4 michaels 231: F1->setusedc = 0;
232: F2->setusedc = 0;
1.1 deraadt 233: slurp(F1);
234: slurp(F2);
1.5 michaels 235: F1->set->cfieldc = 0;
236: F2->set->cfieldc = 0;
1.4 michaels 237:
1.5 michaels 238: /*
239: * We try to let the files have the same field value, advancing
240: * whoever falls behind and always advancing the file(s) we output
241: * from.
242: */
1.1 deraadt 243: while (F1->setcnt && F2->setcnt) {
244: cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
245: if (cval == 0) {
246: /* Oh joy, oh rapture, oh beauty divine! */
247: if (joinout)
248: joinlines(F1, F2);
1.5 michaels 249: slurp(F1);
1.1 deraadt 250: slurp(F2);
1.4 michaels 251: }
252: else {
1.5 michaels 253: if (F1->unpair && (cval < 0 || F2->set->cfieldc == F2->setusedc -1)) {
1.1 deraadt 254: joinlines(F1, NULL);
1.4 michaels 255: slurp(F1);
256: }
257: else if (cval < 0)
1.5 michaels 258: /* File 1 takes the lead... */
1.4 michaels 259: slurp(F1);
1.5 michaels 260: if (F2->unpair && (cval > 0 || F1->set->cfieldc == F1->setusedc -1)) {
1.1 deraadt 261: joinlines(F2, NULL);
1.4 michaels 262: slurp(F2);
263: }
1.5 michaels 264: else if (cval > 0)
265: /* File 2 takes the lead... */
1.4 michaels 266: slurp(F2);
1.1 deraadt 267: }
268: }
269:
270: /*
271: * Now that one of the files is used up, optionally output any
272: * remaining lines from the other file.
273: */
274: if (F1->unpair)
275: while (F1->setcnt) {
276: joinlines(F1, NULL);
277: slurp(F1);
278: }
279: if (F2->unpair)
280: while (F2->setcnt) {
281: joinlines(F2, NULL);
282: slurp(F2);
283: }
1.5 michaels 284:
1.4 michaels 285: return 0;
1.1 deraadt 286: }
287:
1.5 michaels 288: /* wrapper around slurpit() to keep track of what field we are on */
1.4 michaels 289: void slurp(F)
290: INPUT *F;
291: {
292: long fpos;
293: u_long cfieldc;
294:
295: if (F->set == NULL) {
296: fpos = 0;
297: cfieldc = 0;
298: }
299: else {
300: fpos = F->set->fpos;
301: cfieldc = F->set->cfieldc;
302: }
303: slurpit(F);
304: if (F->set == NULL)
305: return;
306: else if (fpos != F->set->fpos)
307: F->set->cfieldc = cfieldc+1;
308: }
309:
1.1 deraadt 310: void
1.4 michaels 311: slurpit(F)
1.1 deraadt 312: INPUT *F;
313: {
1.4 michaels 314: LINE *lp, *lastlp, tmp;
1.1 deraadt 315: size_t len;
316: int cnt;
317: char *bp, *fieldp;
1.4 michaels 318: long fpos;
1.1 deraadt 319: /*
320: * Read all of the lines from an input file that have the same
321: * join field.
322: */
1.4 michaels 323:
1.1 deraadt 324: F->setcnt = 0;
1.4 michaels 325: for (lastlp = NULL; ; ++F->setcnt, lastlp = lp) {
1.1 deraadt 326: /*
327: * If we're out of space to hold line structures, allocate
328: * more. Initialize the structure so that we know that this
329: * is new space.
330: */
331: if (F->setcnt == F->setalloc) {
332: cnt = F->setalloc;
1.4 michaels 333: F->setalloc += 50;
1.1 deraadt 334: if ((F->set = realloc(F->set,
335: F->setalloc * sizeof(LINE))) == NULL)
1.4 michaels 336: err(1, NULL);
337: memset(F->set + cnt, 0, 50 * sizeof(LINE));
338: /* re-set lastlp in case it moved */
339: if (lastlp != NULL)
340: lastlp = &F->set[F->setcnt - 1];
1.1 deraadt 341: }
342: /*
343: * Get any pushed back line, else get the next line. Allocate
344: * space as necessary. If taking the line from the stack swap
1.4 michaels 345: * the two structures so that we don't lose space allocated to
346: * either structure. This could be avoided by doing another
347: * level of indirection, but it's probably okay as is.
1.1 deraadt 348: * but it's probably okay as is.
349: */
350: lp = &F->set[F->setcnt];
1.4 michaels 351: if (F->pushbool) {
1.1 deraadt 352: tmp = F->set[F->setcnt];
353: F->set[F->setcnt] = F->set[F->pushback];
354: F->set[F->pushback] = tmp;
1.4 michaels 355: F->pushbool = 0;
1.1 deraadt 356: continue;
357: }
358: if ((bp = fgetln(F->fp, &len)) == NULL)
359: return;
1.4 michaels 360: /*
361: * we depend on knowing on what field we are, one safe way is the
1.5 michaels 362: * file position, though we should perhaps find another way so we
363: * won't have to call ftell() after each line read from file.
1.4 michaels 364: */
365: fpos = ftell(F->fp) - len;
1.1 deraadt 366: if (lp->linealloc <= len + 1) {
1.4 michaels 367: lp->linealloc += MAX(100, len + 1 - lp->linealloc);
368: if ((lp->line = realloc(lp->line, lp->linealloc)) == NULL)
369: err(1, NULL);
370: }
371: F->setusedc++;
372: memmove(lp->line, bp, len);
373: lp->fpos = fpos;
374: /* Replace trailing newline, if it exists. */
1.1 deraadt 375: if (bp[len - 1] == '\n')
376: lp->line[len - 1] = '\0';
377: else
378: lp->line[len] = '\0';
379: bp = lp->line;
380:
381: /* Split the line into fields, allocate space as necessary. */
382: lp->fieldcnt = 0;
383: while ((fieldp = strsep(&bp, tabchar)) != NULL) {
384: if (spans && *fieldp == '\0')
385: continue;
386: if (lp->fieldcnt == lp->fieldalloc) {
1.4 michaels 387: lp->fieldalloc += 50;
1.1 deraadt 388: if ((lp->fields = realloc(lp->fields,
389: lp->fieldalloc * sizeof(char *))) == NULL)
1.4 michaels 390: err(1, NULL);
1.1 deraadt 391: }
392: lp->fields[lp->fieldcnt++] = fieldp;
393: }
394:
395: /* See if the join field value has changed. */
396: if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
1.4 michaels 397: F->pushbool = 1;
1.1 deraadt 398: F->pushback = F->setcnt;
399: break;
400: }
401: }
402: }
403:
404: int
405: cmp(lp1, fieldno1, lp2, fieldno2)
406: LINE *lp1, *lp2;
407: u_long fieldno1, fieldno2;
408: {
1.4 michaels 409: if (lp1->fieldcnt <= fieldno1)
1.1 deraadt 410: return (-1);
1.4 michaels 411: else if (lp2->fieldcnt <= fieldno2)
412: return (1);
1.1 deraadt 413: return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
414: }
415:
416: void
417: joinlines(F1, F2)
1.4 michaels 418: INPUT *F1, *F2;
1.1 deraadt 419: {
1.4 michaels 420: int cnt1, cnt2;
1.1 deraadt 421:
422: /*
423: * Output the results of a join comparison. The output may be from
424: * either file 1 or file 2 (in which case the first argument is the
425: * file from which to output) or from both.
426: */
427: if (F2 == NULL) {
428: for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
429: outoneline(F1, &F1->set[cnt1]);
430: return;
431: }
432: for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
433: for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
434: outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
435: }
436:
437: void
438: outoneline(F, lp)
439: INPUT *F;
1.4 michaels 440: LINE *lp;
1.1 deraadt 441: {
1.4 michaels 442: int cnt;
1.1 deraadt 443:
444: /*
445: * Output a single line from one of the files, according to the
446: * join rules. This happens when we are writing unmatched single
447: * lines. Output empty fields in the right places.
448: */
449: if (olist)
450: for (cnt = 0; cnt < olistcnt; ++cnt) {
1.4 michaels 451: if (olist[cnt].filenum == F->number)
452: outfield(lp, olist[cnt].fieldno, 0);
453: else
454: outfield(lp, 0, 1);
1.1 deraadt 455: }
456: else
457: for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
1.4 michaels 458: outfield(lp, cnt, 0);
459: putchar('\n');
1.1 deraadt 460: if (ferror(stdout))
1.4 michaels 461: err(1, "stdout");
1.1 deraadt 462: needsep = 0;
463: }
464:
465: void
466: outtwoline(F1, lp1, F2, lp2)
1.4 michaels 467: INPUT *F1, *F2;
468: LINE *lp1, *lp2;
1.1 deraadt 469: {
1.4 michaels 470: int cnt;
1.1 deraadt 471:
472: /* Output a pair of lines according to the join list (if any). */
473: if (olist)
474: for (cnt = 0; cnt < olistcnt; ++cnt)
1.4 michaels 475: if (olist[cnt].filenum == 1)
476: outfield(lp1, olist[cnt].fieldno, 0);
477: else /* if (olist[cnt].filenum == 2) */
478: outfield(lp2, olist[cnt].fieldno, 0);
1.1 deraadt 479: else {
480: /*
481: * Output the join field, then the remaining fields from F1
482: * and F2.
483: */
1.4 michaels 484: outfield(lp1, F1->joinf, 0);
1.1 deraadt 485: for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
486: if (F1->joinf != cnt)
1.4 michaels 487: outfield(lp1, cnt, 0);
1.1 deraadt 488: for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
489: if (F2->joinf != cnt)
1.4 michaels 490: outfield(lp2, cnt, 0);
1.1 deraadt 491: }
1.4 michaels 492: putchar('\n');
1.1 deraadt 493: if (ferror(stdout))
1.4 michaels 494: err(1, "stdout");
1.1 deraadt 495: needsep = 0;
496: }
497:
498: void
1.4 michaels 499: outfield(lp, fieldno, out_empty)
1.1 deraadt 500: LINE *lp;
501: u_long fieldno;
1.4 michaels 502: int out_empty;
1.1 deraadt 503: {
504: if (needsep++)
1.4 michaels 505: putchar((int)*tabchar);
1.1 deraadt 506: if (!ferror(stdout))
1.4 michaels 507: if (lp->fieldcnt < fieldno || out_empty) {
1.1 deraadt 508: if (empty != NULL)
1.4 michaels 509: fputs(empty, stdout);
1.1 deraadt 510: } else {
511: if (*lp->fields[fieldno] == '\0')
512: return;
1.4 michaels 513: fputs(lp->fields[fieldno], stdout);
1.1 deraadt 514: }
515: if (ferror(stdout))
1.4 michaels 516: err(1, "stdout");
1.1 deraadt 517: }
518:
519: /*
520: * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
521: * fields.
522: */
523: void
524: fieldarg(option)
525: char *option;
526: {
527: u_long fieldno;
528: char *end, *token;
529:
1.4 michaels 530: while ((token = strsep(&option, ", \t")) != NULL) {
1.1 deraadt 531: if (*token == '\0')
532: continue;
1.4 michaels 533: if ((token[0] != '1' && token[0] != '2') || token[1] != '.')
534: errx(1, "malformed -o option field");
1.1 deraadt 535: fieldno = strtol(token + 2, &end, 10);
536: if (*end)
1.4 michaels 537: errx(1, "malformed -o option field");
1.1 deraadt 538: if (fieldno == 0)
1.4 michaels 539: errx(1, "field numbers are 1 based");
1.1 deraadt 540: if (olistcnt == olistalloc) {
541: olistalloc += 50;
542: if ((olist = realloc(olist,
543: olistalloc * sizeof(OLIST))) == NULL)
1.4 michaels 544: err(1, NULL);
1.1 deraadt 545: }
1.4 michaels 546: olist[olistcnt].filenum = token[0] - '0';
1.1 deraadt 547: olist[olistcnt].fieldno = fieldno - 1;
548: ++olistcnt;
549: }
550: }
551:
552: void
553: obsolete(argv)
554: char **argv;
555: {
556: int len;
557: char **p, *ap, *t;
558:
1.4 michaels 559: while ((ap = *++argv) != NULL) {
1.1 deraadt 560: /* Return if "--". */
561: if (ap[0] == '-' && ap[1] == '-')
562: return;
563: switch (ap[1]) {
564: case 'a':
565: /*
566: * The original join allowed "-a", which meant the
567: * same as -a1 plus -a2. POSIX 1003.2, Draft 11.2
568: * only specifies this as "-a 1" and "a -2", so we
569: * have to use another option flag, one that is
570: * unlikely to ever be used or accidentally entered
571: * on the command line. (Well, we could reallocate
572: * the argv array, but that hardly seems worthwhile.)
573: */
574: if (ap[2] == '\0')
575: ap[1] = '\01';
576: break;
577: case 'j':
578: /*
579: * The original join allowed "-j[12] arg" and "-j arg".
580: * Convert the former to "-[12] arg". Don't convert
581: * the latter since getopt(3) can handle it.
582: */
583: switch(ap[2]) {
584: case '1':
585: if (ap[3] != '\0')
586: goto jbad;
587: ap[1] = '1';
588: ap[2] = '\0';
589: break;
590: case '2':
591: if (ap[3] != '\0')
592: goto jbad;
593: ap[1] = '2';
594: ap[2] = '\0';
595: break;
596: case '\0':
597: break;
598: default:
1.4 michaels 599: jbad: errx(1, "illegal option -- %s", ap);
1.1 deraadt 600: usage();
601: }
602: break;
603: case 'o':
604: /*
1.4 michaels 605: * The original join allowed "-o arg arg".
606: * Convert to "-o arg -o arg".
1.1 deraadt 607: */
608: if (ap[2] != '\0')
609: break;
1.4 michaels 610: for (p = argv + 2; *p != NULL; ++p) {
611: if ((p[0][0] != '1' && p[0][0] != '2') || p[0][1] != '.')
1.1 deraadt 612: break;
613: len = strlen(*p);
614: if (len - 2 != strspn(*p + 2, "0123456789"))
615: break;
616: if ((t = malloc(len + 3)) == NULL)
1.4 michaels 617: err(1, NULL);
1.1 deraadt 618: t[0] = '-';
619: t[1] = 'o';
1.4 michaels 620: memmove(t + 2, *p, len + 1);
1.1 deraadt 621: *p = t;
622: }
623: argv = p - 1;
624: break;
625: }
626: }
627: }
628:
629: void
630: usage()
631: {
632: (void)fprintf(stderr, "%s%s\n",
633: "usage: join [-a fileno | -v fileno ] [-e string] [-1 field] ",
634: "[-2 field]\n [-o list] [-t char] file1 file2");
635: exit(1);
636: }