Annotation of src/usr.bin/join/join.c, Revision 1.10
1.10 ! deraadt 1: /* $Id: join.c,v 1.9 1997/07/24 01:35:30 deraadt 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.10 ! deraadt 48: static char rcsid[] = "$Id: join.c,v 1.9 1997/07/24 01:35:30 deraadt 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.7 michaels 68: char *line; /* line */
1.4 michaels 69: u_long linealloc; /* line allocated count */
70: char **fields; /* line field(s) */
71: u_long fieldcnt; /* line field(s) count */
1.7 michaels 72: u_long fieldalloc; /* line field(s) allocated count */
73: u_long cfieldc; /* current field count */
1.4 michaels 74: long fpos; /* fpos of start of field */
1.1 deraadt 75: } LINE;
76:
77: typedef struct {
1.7 michaels 78: FILE *fp; /* file descriptor */
1.4 michaels 79: u_long joinf; /* join field (-1, -2, -j) */
1.7 michaels 80: int unpair; /* output unpairable lines (-a) */
81: int number; /* 1 for file 1, 2 for file 2 */
82: LINE *set; /* set of lines with same field */
1.4 michaels 83: int pushbool; /* if pushback is set */
84: u_long pushback; /* line on the stack */
85: u_long setcnt; /* set count */
86: u_long setalloc; /* set allocated count */
87: u_long setusedc; /* sets used */
1.1 deraadt 88: } INPUT;
1.5 michaels 89: INPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, },
90: input2 = { NULL, 0, 0, 2, NULL, 0, 0, 0, };
1.1 deraadt 91:
92: typedef struct {
1.4 michaels 93: u_long filenum; /* file number */
1.1 deraadt 94: u_long fieldno; /* field number */
95: } OLIST;
96: OLIST *olist; /* output field list */
97: u_long olistcnt; /* output field list count */
98: u_long olistalloc; /* output field allocated count */
99:
100: int joinout = 1; /* show lines with matched join fields (-v) */
101: int needsep; /* need separator character */
102: int spans = 1; /* span multiple delimiters (-t) */
103: char *empty; /* empty field replacement string (-e) */
104: char *tabchar = " \t"; /* delimiter characters (-t) */
105:
106: int cmp __P((LINE *, u_long, LINE *, u_long));
107: void fieldarg __P((char *));
108: void joinlines __P((INPUT *, INPUT *));
109: void obsolete __P((char **));
1.4 michaels 110: void outfield __P((LINE *, u_long, int));
1.1 deraadt 111: void outoneline __P((INPUT *, LINE *));
112: void outtwoline __P((INPUT *, LINE *, INPUT *, LINE *));
113: void slurp __P((INPUT *));
1.4 michaels 114: void slurpit __P((INPUT *));
1.1 deraadt 115: void usage __P((void));
116:
117: int
118: main(argc, argv)
119: int argc;
120: char *argv[];
121: {
1.4 michaels 122: INPUT *F1, *F2;
1.1 deraadt 123: int aflag, ch, cval, vflag;
124: char *end;
125:
126: F1 = &input1;
127: F2 = &input2;
128:
129: aflag = vflag = 0;
130: obsolete(argv);
1.6 millert 131: while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
1.1 deraadt 132: switch (ch) {
1.4 michaels 133: case '\01': /* See comment in obsolete(). */
1.1 deraadt 134: aflag = 1;
135: F1->unpair = F2->unpair = 1;
136: break;
137: case '1':
138: if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
1.4 michaels 139: errx(1, "-1 option field number less than 1");
1.1 deraadt 140: if (*end)
1.4 michaels 141: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 142: --F1->joinf;
143: break;
144: case '2':
145: if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
1.4 michaels 146: errx(1, "-2 option field number less than 1");
1.1 deraadt 147: if (*end)
1.4 michaels 148: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 149: --F2->joinf;
150: break;
151: case 'a':
152: aflag = 1;
153: switch(strtol(optarg, &end, 10)) {
154: case 1:
155: F1->unpair = 1;
156: break;
157: case 2:
158: F2->unpair = 1;
159: break;
160: default:
1.4 michaels 161: errx(1, "-a option file number not 1 or 2");
1.1 deraadt 162: break;
163: }
164: if (*end)
1.4 michaels 165: errx(1, "illegal file number -- %s", optarg);
1.1 deraadt 166: break;
167: case 'e':
168: empty = optarg;
169: break;
170: case 'j':
1.4 michaels 171: if ((F1->joinf = F2->joinf = strtol(optarg, &end, 10)) < 1)
172: errx(1, "-j option field number less than 1");
1.1 deraadt 173: if (*end)
1.4 michaels 174: errx(1, "illegal field number -- %s", optarg);
1.1 deraadt 175: --F1->joinf;
176: --F2->joinf;
177: break;
178: case 'o':
179: fieldarg(optarg);
180: break;
181: case 't':
182: spans = 0;
183: if (strlen(tabchar = optarg) != 1)
1.4 michaels 184: errx(1, "illegal tab character specification");
1.1 deraadt 185: break;
186: case 'v':
187: vflag = 1;
188: joinout = 0;
1.4 michaels 189: switch (strtol(optarg, &end, 10)) {
1.1 deraadt 190: case 1:
191: F1->unpair = 1;
192: break;
193: case 2:
194: F2->unpair = 1;
195: break;
196: default:
1.4 michaels 197: errx(1, "-v option file number not 1 or 2");
1.1 deraadt 198: break;
199: }
200: if (*end)
1.4 michaels 201: errx(1, "illegal file number -- %s", optarg);
1.1 deraadt 202: break;
203: case '?':
204: default:
205: usage();
206: }
207: }
208: argc -= optind;
209: argv += optind;
210:
211: if (aflag && vflag)
1.4 michaels 212: errx(1, "the -a and -v options are mutually exclusive");
1.1 deraadt 213:
214: if (argc != 2)
215: usage();
216:
217: /* Open the files; "-" means stdin. */
218: if (!strcmp(*argv, "-"))
219: F1->fp = stdin;
220: else if ((F1->fp = fopen(*argv, "r")) == NULL)
1.4 michaels 221: err(1, "%s", *argv);
1.1 deraadt 222: ++argv;
223: if (!strcmp(*argv, "-"))
224: F2->fp = stdin;
225: else if ((F2->fp = fopen(*argv, "r")) == NULL)
1.4 michaels 226: err(1, "%s", *argv);
1.1 deraadt 227: if (F1->fp == stdin && F2->fp == stdin)
1.4 michaels 228: errx(1, "only one input file may be stdin");
1.1 deraadt 229:
1.4 michaels 230: F1->setusedc = 0;
231: F2->setusedc = 0;
1.1 deraadt 232: slurp(F1);
233: slurp(F2);
1.5 michaels 234: F1->set->cfieldc = 0;
235: F2->set->cfieldc = 0;
1.4 michaels 236:
1.5 michaels 237: /*
238: * We try to let the files have the same field value, advancing
239: * whoever falls behind and always advancing the file(s) we output
240: * from.
241: */
1.1 deraadt 242: while (F1->setcnt && F2->setcnt) {
243: cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
244: if (cval == 0) {
245: /* Oh joy, oh rapture, oh beauty divine! */
246: if (joinout)
247: joinlines(F1, F2);
1.5 michaels 248: slurp(F1);
1.1 deraadt 249: slurp(F2);
1.4 michaels 250: }
251: else {
1.7 michaels 252: if (F1->unpair
253: && (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.7 michaels 260: if (F2->unpair
261: && (cval > 0 || F1->set->cfieldc == F1->setusedc -1)) {
1.1 deraadt 262: joinlines(F2, NULL);
1.4 michaels 263: slurp(F2);
264: }
1.5 michaels 265: else if (cval > 0)
266: /* File 2 takes the lead... */
1.4 michaels 267: slurp(F2);
1.1 deraadt 268: }
269: }
270:
271: /*
272: * Now that one of the files is used up, optionally output any
273: * remaining lines from the other file.
274: */
275: if (F1->unpair)
276: while (F1->setcnt) {
277: joinlines(F1, NULL);
278: slurp(F1);
279: }
280: if (F2->unpair)
281: while (F2->setcnt) {
282: joinlines(F2, NULL);
283: slurp(F2);
284: }
1.5 michaels 285:
1.4 michaels 286: return 0;
1.1 deraadt 287: }
288:
1.5 michaels 289: /* wrapper around slurpit() to keep track of what field we are on */
1.4 michaels 290: void slurp(F)
291: INPUT *F;
292: {
293: long fpos;
294: u_long cfieldc;
295:
296: if (F->set == NULL) {
297: fpos = 0;
298: cfieldc = 0;
299: }
300: else {
301: fpos = F->set->fpos;
302: cfieldc = F->set->cfieldc;
303: }
304: slurpit(F);
305: if (F->set == NULL)
306: return;
307: else if (fpos != F->set->fpos)
308: F->set->cfieldc = cfieldc+1;
309: }
310:
1.1 deraadt 311: void
1.4 michaels 312: slurpit(F)
1.1 deraadt 313: INPUT *F;
314: {
1.4 michaels 315: LINE *lp, *lastlp, tmp;
1.1 deraadt 316: size_t len;
317: int cnt;
318: char *bp, *fieldp;
1.4 michaels 319: long fpos;
1.1 deraadt 320: /*
321: * Read all of the lines from an input file that have the same
322: * join field.
323: */
1.4 michaels 324:
1.1 deraadt 325: F->setcnt = 0;
1.4 michaels 326: for (lastlp = NULL; ; ++F->setcnt, lastlp = lp) {
1.1 deraadt 327: /*
328: * If we're out of space to hold line structures, allocate
329: * more. Initialize the structure so that we know that this
330: * is new space.
331: */
332: if (F->setcnt == F->setalloc) {
333: cnt = F->setalloc;
1.4 michaels 334: F->setalloc += 50;
1.1 deraadt 335: if ((F->set = realloc(F->set,
336: F->setalloc * sizeof(LINE))) == NULL)
1.4 michaels 337: err(1, NULL);
338: memset(F->set + cnt, 0, 50 * sizeof(LINE));
339: /* re-set lastlp in case it moved */
340: if (lastlp != NULL)
341: lastlp = &F->set[F->setcnt - 1];
1.1 deraadt 342: }
343: /*
344: * Get any pushed back line, else get the next line. Allocate
345: * space as necessary. If taking the line from the stack swap
1.4 michaels 346: * the two structures so that we don't lose space allocated to
347: * either structure. This could be avoided by doing another
348: * level of indirection, but it's probably okay as is.
1.1 deraadt 349: * but it's probably okay as is.
350: */
351: lp = &F->set[F->setcnt];
1.4 michaels 352: if (F->pushbool) {
1.1 deraadt 353: tmp = F->set[F->setcnt];
354: F->set[F->setcnt] = F->set[F->pushback];
355: F->set[F->pushback] = tmp;
1.4 michaels 356: F->pushbool = 0;
1.1 deraadt 357: continue;
358: }
359: if ((bp = fgetln(F->fp, &len)) == NULL)
360: return;
1.4 michaels 361: /*
1.7 michaels 362: * we depend on knowing on what field we are, one safe way is
363: * the file position.
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);
1.7 michaels 368: if ((lp->line = realloc(lp->line, lp->linealloc))
369: == NULL)
1.4 michaels 370: err(1, NULL);
371: }
372: F->setusedc++;
373: memmove(lp->line, bp, len);
374: lp->fpos = fpos;
375: /* Replace trailing newline, if it exists. */
1.1 deraadt 376: if (bp[len - 1] == '\n')
377: lp->line[len - 1] = '\0';
378: else
379: lp->line[len] = '\0';
380: bp = lp->line;
381:
382: /* Split the line into fields, allocate space as necessary. */
383: lp->fieldcnt = 0;
384: while ((fieldp = strsep(&bp, tabchar)) != NULL) {
385: if (spans && *fieldp == '\0')
386: continue;
387: if (lp->fieldcnt == lp->fieldalloc) {
1.4 michaels 388: lp->fieldalloc += 50;
1.1 deraadt 389: if ((lp->fields = realloc(lp->fields,
390: lp->fieldalloc * sizeof(char *))) == NULL)
1.4 michaels 391: err(1, NULL);
1.1 deraadt 392: }
393: lp->fields[lp->fieldcnt++] = fieldp;
394: }
395:
396: /* See if the join field value has changed. */
397: if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
1.4 michaels 398: F->pushbool = 1;
1.1 deraadt 399: F->pushback = F->setcnt;
400: break;
401: }
402: }
403: }
404:
405: int
406: cmp(lp1, fieldno1, lp2, fieldno2)
407: LINE *lp1, *lp2;
408: u_long fieldno1, fieldno2;
409: {
1.4 michaels 410: if (lp1->fieldcnt <= fieldno1)
1.1 deraadt 411: return (-1);
1.4 michaels 412: else if (lp2->fieldcnt <= fieldno2)
413: return (1);
1.1 deraadt 414: return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
415: }
416:
417: void
418: joinlines(F1, F2)
1.4 michaels 419: INPUT *F1, *F2;
1.1 deraadt 420: {
1.4 michaels 421: int cnt1, cnt2;
1.1 deraadt 422:
423: /*
424: * Output the results of a join comparison. The output may be from
425: * either file 1 or file 2 (in which case the first argument is the
426: * file from which to output) or from both.
427: */
428: if (F2 == NULL) {
429: for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
430: outoneline(F1, &F1->set[cnt1]);
431: return;
432: }
433: for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
434: for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
435: outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
436: }
437:
438: void
439: outoneline(F, lp)
440: INPUT *F;
1.4 michaels 441: LINE *lp;
1.1 deraadt 442: {
1.4 michaels 443: int cnt;
1.1 deraadt 444:
445: /*
446: * Output a single line from one of the files, according to the
447: * join rules. This happens when we are writing unmatched single
448: * lines. Output empty fields in the right places.
449: */
450: if (olist)
451: for (cnt = 0; cnt < olistcnt; ++cnt) {
1.4 michaels 452: if (olist[cnt].filenum == F->number)
453: outfield(lp, olist[cnt].fieldno, 0);
454: else
455: outfield(lp, 0, 1);
1.1 deraadt 456: }
457: else
458: for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
1.4 michaels 459: outfield(lp, cnt, 0);
460: putchar('\n');
1.1 deraadt 461: if (ferror(stdout))
1.4 michaels 462: err(1, "stdout");
1.1 deraadt 463: needsep = 0;
464: }
465:
466: void
467: outtwoline(F1, lp1, F2, lp2)
1.4 michaels 468: INPUT *F1, *F2;
469: LINE *lp1, *lp2;
1.1 deraadt 470: {
1.4 michaels 471: int cnt;
1.1 deraadt 472:
473: /* Output a pair of lines according to the join list (if any). */
474: if (olist)
475: for (cnt = 0; cnt < olistcnt; ++cnt)
1.4 michaels 476: if (olist[cnt].filenum == 1)
477: outfield(lp1, olist[cnt].fieldno, 0);
478: else /* if (olist[cnt].filenum == 2) */
479: outfield(lp2, olist[cnt].fieldno, 0);
1.1 deraadt 480: else {
481: /*
482: * Output the join field, then the remaining fields from F1
483: * and F2.
484: */
1.4 michaels 485: outfield(lp1, F1->joinf, 0);
1.1 deraadt 486: for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
487: if (F1->joinf != cnt)
1.4 michaels 488: outfield(lp1, cnt, 0);
1.1 deraadt 489: for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
490: if (F2->joinf != cnt)
1.4 michaels 491: outfield(lp2, cnt, 0);
1.1 deraadt 492: }
1.4 michaels 493: putchar('\n');
1.1 deraadt 494: if (ferror(stdout))
1.4 michaels 495: err(1, "stdout");
1.1 deraadt 496: needsep = 0;
497: }
498:
499: void
1.4 michaels 500: outfield(lp, fieldno, out_empty)
1.1 deraadt 501: LINE *lp;
502: u_long fieldno;
1.4 michaels 503: int out_empty;
1.1 deraadt 504: {
505: if (needsep++)
1.4 michaels 506: putchar((int)*tabchar);
1.1 deraadt 507: if (!ferror(stdout))
1.4 michaels 508: if (lp->fieldcnt < fieldno || out_empty) {
1.1 deraadt 509: if (empty != NULL)
1.4 michaels 510: fputs(empty, stdout);
1.1 deraadt 511: } else {
512: if (*lp->fields[fieldno] == '\0')
513: return;
1.4 michaels 514: fputs(lp->fields[fieldno], stdout);
1.1 deraadt 515: }
516: if (ferror(stdout))
1.4 michaels 517: err(1, "stdout");
1.1 deraadt 518: }
519:
520: /*
521: * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
522: * fields.
523: */
524: void
525: fieldarg(option)
526: char *option;
527: {
528: u_long fieldno;
529: char *end, *token;
530:
1.4 michaels 531: while ((token = strsep(&option, ", \t")) != NULL) {
1.1 deraadt 532: if (*token == '\0')
533: continue;
1.4 michaels 534: if ((token[0] != '1' && token[0] != '2') || token[1] != '.')
535: errx(1, "malformed -o option field");
1.1 deraadt 536: fieldno = strtol(token + 2, &end, 10);
537: if (*end)
1.4 michaels 538: errx(1, "malformed -o option field");
1.1 deraadt 539: if (fieldno == 0)
1.4 michaels 540: errx(1, "field numbers are 1 based");
1.1 deraadt 541: if (olistcnt == olistalloc) {
542: olistalloc += 50;
543: if ((olist = realloc(olist,
544: olistalloc * sizeof(OLIST))) == NULL)
1.4 michaels 545: err(1, NULL);
1.1 deraadt 546: }
1.4 michaels 547: olist[olistcnt].filenum = token[0] - '0';
1.1 deraadt 548: olist[olistcnt].fieldno = fieldno - 1;
549: ++olistcnt;
550: }
551: }
552:
553: void
554: obsolete(argv)
555: char **argv;
556: {
557: int len;
558: char **p, *ap, *t;
559:
1.4 michaels 560: while ((ap = *++argv) != NULL) {
1.1 deraadt 561: /* Return if "--". */
1.9 deraadt 562: if (ap[0] == '-' && ap[1] == '-')
1.1 deraadt 563: return;
1.10 ! deraadt 564: /* skip if not an option */
! 565: if (ap[0] != '-')
! 566: continue;
1.1 deraadt 567: switch (ap[1]) {
568: case 'a':
569: /*
570: * The original join allowed "-a", which meant the
571: * same as -a1 plus -a2. POSIX 1003.2, Draft 11.2
572: * only specifies this as "-a 1" and "a -2", so we
573: * have to use another option flag, one that is
574: * unlikely to ever be used or accidentally entered
575: * on the command line. (Well, we could reallocate
576: * the argv array, but that hardly seems worthwhile.)
577: */
578: if (ap[2] == '\0')
579: ap[1] = '\01';
580: break;
581: case 'j':
582: /*
583: * The original join allowed "-j[12] arg" and "-j arg".
584: * Convert the former to "-[12] arg". Don't convert
585: * the latter since getopt(3) can handle it.
586: */
587: switch(ap[2]) {
588: case '1':
589: if (ap[3] != '\0')
590: goto jbad;
591: ap[1] = '1';
592: ap[2] = '\0';
593: break;
594: case '2':
595: if (ap[3] != '\0')
596: goto jbad;
597: ap[1] = '2';
598: ap[2] = '\0';
599: break;
600: case '\0':
601: break;
602: default:
1.4 michaels 603: jbad: errx(1, "illegal option -- %s", ap);
1.1 deraadt 604: usage();
605: }
606: break;
607: case 'o':
608: /*
1.4 michaels 609: * The original join allowed "-o arg arg".
610: * Convert to "-o arg -o arg".
1.1 deraadt 611: */
612: if (ap[2] != '\0')
613: break;
1.4 michaels 614: for (p = argv + 2; *p != NULL; ++p) {
615: if ((p[0][0] != '1' && p[0][0] != '2') || p[0][1] != '.')
1.1 deraadt 616: break;
617: len = strlen(*p);
618: if (len - 2 != strspn(*p + 2, "0123456789"))
619: break;
620: if ((t = malloc(len + 3)) == NULL)
1.4 michaels 621: err(1, NULL);
1.1 deraadt 622: t[0] = '-';
623: t[1] = 'o';
1.4 michaels 624: memmove(t + 2, *p, len + 1);
1.1 deraadt 625: *p = t;
626: }
627: argv = p - 1;
628: break;
629: }
630: }
631: }
632:
633: void
634: usage()
635: {
636: (void)fprintf(stderr, "%s%s\n",
637: "usage: join [-a fileno | -v fileno ] [-e string] [-1 field] ",
638: "[-2 field]\n [-o list] [-t char] file1 file2");
639: exit(1);
640: }