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