xref: /freebsd/usr.bin/join/join.c (revision 9ef5c48befdfa36ad5ef5e0ae898e7ca9397abcc)
19b50d902SRodney W. Grimes /*-
29b50d902SRodney W. Grimes  * Copyright (c) 1991, 1993, 1994
39b50d902SRodney W. Grimes  *	The Regents of the University of California.  All rights reserved.
49b50d902SRodney W. Grimes  *
59b50d902SRodney W. Grimes  * This code is derived from software contributed to Berkeley by
69b50d902SRodney W. Grimes  * Steve Hayman of the Computer Science Department, Indiana University,
79b50d902SRodney W. Grimes  * Michiro Hikida and David Goodenough.
89b50d902SRodney W. Grimes  *
99b50d902SRodney W. Grimes  * Redistribution and use in source and binary forms, with or without
109b50d902SRodney W. Grimes  * modification, are permitted provided that the following conditions
119b50d902SRodney W. Grimes  * are met:
129b50d902SRodney W. Grimes  * 1. Redistributions of source code must retain the above copyright
139b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer.
149b50d902SRodney W. Grimes  * 2. Redistributions in binary form must reproduce the above copyright
159b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer in the
169b50d902SRodney W. Grimes  *    documentation and/or other materials provided with the distribution.
179b50d902SRodney W. Grimes  * 3. All advertising materials mentioning features or use of this software
189b50d902SRodney W. Grimes  *    must display the following acknowledgement:
199b50d902SRodney W. Grimes  *	This product includes software developed by the University of
209b50d902SRodney W. Grimes  *	California, Berkeley and its contributors.
219b50d902SRodney W. Grimes  * 4. Neither the name of the University nor the names of its contributors
229b50d902SRodney W. Grimes  *    may be used to endorse or promote products derived from this software
239b50d902SRodney W. Grimes  *    without specific prior written permission.
249b50d902SRodney W. Grimes  *
259b50d902SRodney W. Grimes  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
269b50d902SRodney W. Grimes  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
279b50d902SRodney W. Grimes  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
289b50d902SRodney W. Grimes  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
299b50d902SRodney W. Grimes  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
309b50d902SRodney W. Grimes  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
319b50d902SRodney W. Grimes  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
329b50d902SRodney W. Grimes  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
339b50d902SRodney W. Grimes  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
349b50d902SRodney W. Grimes  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
359b50d902SRodney W. Grimes  * SUCH DAMAGE.
369b50d902SRodney W. Grimes  */
379b50d902SRodney W. Grimes 
389b50d902SRodney W. Grimes #ifndef lint
3921a3d165SPhilippe Charnier static const char copyright[] =
409b50d902SRodney W. Grimes "@(#) Copyright (c) 1991, 1993, 1994\n\
419b50d902SRodney W. Grimes 	The Regents of the University of California.  All rights reserved.\n";
429b50d902SRodney W. Grimes #endif /* not lint */
439b50d902SRodney W. Grimes 
449b50d902SRodney W. Grimes #ifndef lint
4521a3d165SPhilippe Charnier #if 0
466f0a860fSPeter Wemm static char sccsid[] = "@(#)join.c	8.6 (Berkeley) 5/4/95";
4721a3d165SPhilippe Charnier #endif
4821a3d165SPhilippe Charnier static const char rcsid[] =
499ef5c48bSBill Fumerola 	"$Id: join.c,v 1.8 1997/08/19 15:58:15 jlemon Exp $";
509b50d902SRodney W. Grimes #endif /* not lint */
519b50d902SRodney W. Grimes 
529b50d902SRodney W. Grimes #include <sys/param.h>
539b50d902SRodney W. Grimes 
549b50d902SRodney W. Grimes #include <ctype.h>
559b50d902SRodney W. Grimes #include <err.h>
569b50d902SRodney W. Grimes #include <errno.h>
579b50d902SRodney W. Grimes #include <stdio.h>
589b50d902SRodney W. Grimes #include <stdlib.h>
599b50d902SRodney W. Grimes #include <string.h>
606f0a860fSPeter Wemm #include <unistd.h>
619b50d902SRodney W. Grimes 
629b50d902SRodney W. Grimes /*
639b50d902SRodney W. Grimes  * There's a structure per input file which encapsulates the state of the
649b50d902SRodney W. Grimes  * file.  We repeatedly read lines from each file until we've read in all
659b50d902SRodney W. Grimes  * the consecutive lines from the file with a common join field.  Then we
669b50d902SRodney W. Grimes  * compare the set of lines with an equivalent set from the other file.
679b50d902SRodney W. Grimes  */
689b50d902SRodney W. Grimes typedef struct {
699b50d902SRodney W. Grimes 	char *line;		/* line */
709b50d902SRodney W. Grimes 	u_long linealloc;	/* line allocated count */
719b50d902SRodney W. Grimes 	char **fields;		/* line field(s) */
729b50d902SRodney W. Grimes 	u_long fieldcnt;	/* line field(s) count */
739b50d902SRodney W. Grimes 	u_long fieldalloc;	/* line field(s) allocated count */
749b50d902SRodney W. Grimes } LINE;
759b50d902SRodney W. Grimes 
769b50d902SRodney W. Grimes typedef struct {
779b50d902SRodney W. Grimes 	FILE *fp;		/* file descriptor */
789b50d902SRodney W. Grimes 	u_long joinf;		/* join field (-1, -2, -j) */
799b50d902SRodney W. Grimes 	int unpair;		/* output unpairable lines (-a) */
809b50d902SRodney W. Grimes 	int number;		/* 1 for file 1, 2 for file 2 */
819b50d902SRodney W. Grimes 
829b50d902SRodney W. Grimes 	LINE *set;		/* set of lines with same field */
839b50d902SRodney W. Grimes 	int pushbool;		/* if pushback is set */
849b50d902SRodney W. Grimes 	u_long pushback;	/* line on the stack */
859b50d902SRodney W. Grimes 	u_long setcnt;		/* set count */
869b50d902SRodney W. Grimes 	u_long setalloc;	/* set allocated count */
879b50d902SRodney W. Grimes } INPUT;
889b50d902SRodney W. Grimes INPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, },
899b50d902SRodney W. Grimes       input2 = { NULL, 0, 0, 2, NULL, 0, 0, 0, };
909b50d902SRodney W. Grimes 
919b50d902SRodney W. Grimes typedef struct {
929b50d902SRodney W. Grimes 	u_long	filenum;	/* file number */
939b50d902SRodney W. Grimes 	u_long	fieldno;	/* field number */
949b50d902SRodney W. Grimes } OLIST;
959b50d902SRodney W. Grimes OLIST *olist;			/* output field list */
969b50d902SRodney W. Grimes u_long olistcnt;		/* output field list count */
979b50d902SRodney W. Grimes u_long olistalloc;		/* output field allocated count */
989b50d902SRodney W. Grimes 
999b50d902SRodney W. Grimes int joinout = 1;		/* show lines with matched join fields (-v) */
1009b50d902SRodney W. Grimes int needsep;			/* need separator character */
1019b50d902SRodney W. Grimes int spans = 1;			/* span multiple delimiters (-t) */
1029b50d902SRodney W. Grimes char *empty;			/* empty field replacement string (-e) */
1039b50d902SRodney W. Grimes char *tabchar = " \t";		/* delimiter characters (-t) */
1049b50d902SRodney W. Grimes 
1059b50d902SRodney W. Grimes int  cmp __P((LINE *, u_long, LINE *, u_long));
1069b50d902SRodney W. Grimes void fieldarg __P((char *));
1079b50d902SRodney W. Grimes void joinlines __P((INPUT *, INPUT *));
1089b50d902SRodney W. Grimes void obsolete __P((char **));
1099b50d902SRodney W. Grimes void outfield __P((LINE *, u_long, int));
1109b50d902SRodney W. Grimes void outoneline __P((INPUT *, LINE *));
1119b50d902SRodney W. Grimes void outtwoline __P((INPUT *, LINE *, INPUT *, LINE *));
1129b50d902SRodney W. Grimes void slurp __P((INPUT *));
1139b50d902SRodney W. Grimes void usage __P((void));
1149b50d902SRodney W. Grimes 
1159b50d902SRodney W. Grimes int
1169b50d902SRodney W. Grimes main(argc, argv)
1179b50d902SRodney W. Grimes 	int argc;
1189b50d902SRodney W. Grimes 	char *argv[];
1199b50d902SRodney W. Grimes {
1209b50d902SRodney W. Grimes 	INPUT *F1, *F2;
1219b50d902SRodney W. Grimes 	int aflag, ch, cval, vflag;
1229b50d902SRodney W. Grimes 	char *end;
1239b50d902SRodney W. Grimes 
1249b50d902SRodney W. Grimes 	F1 = &input1;
1259b50d902SRodney W. Grimes 	F2 = &input2;
1269b50d902SRodney W. Grimes 
1279b50d902SRodney W. Grimes 	aflag = vflag = 0;
1289b50d902SRodney W. Grimes 	obsolete(argv);
1291c8af878SWarner Losh 	while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
1309b50d902SRodney W. Grimes 		switch (ch) {
1319b50d902SRodney W. Grimes 		case '\01':		/* See comment in obsolete(). */
1329b50d902SRodney W. Grimes 			aflag = 1;
1339b50d902SRodney W. Grimes 			F1->unpair = F2->unpair = 1;
1349b50d902SRodney W. Grimes 			break;
1359b50d902SRodney W. Grimes 		case '1':
1369b50d902SRodney W. Grimes 			if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
1379b50d902SRodney W. Grimes 				errx(1, "-1 option field number less than 1");
1389b50d902SRodney W. Grimes 			if (*end)
1399b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1409b50d902SRodney W. Grimes 			--F1->joinf;
1419b50d902SRodney W. Grimes 			break;
1429b50d902SRodney W. Grimes 		case '2':
1439b50d902SRodney W. Grimes 			if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
1449b50d902SRodney W. Grimes 				errx(1, "-2 option field number less than 1");
1459b50d902SRodney W. Grimes 			if (*end)
1469b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1479b50d902SRodney W. Grimes 			--F2->joinf;
1489b50d902SRodney W. Grimes 			break;
1499b50d902SRodney W. Grimes 		case 'a':
1509b50d902SRodney W. Grimes 			aflag = 1;
1519b50d902SRodney W. Grimes 			switch(strtol(optarg, &end, 10)) {
1529b50d902SRodney W. Grimes 			case 1:
1539b50d902SRodney W. Grimes 				F1->unpair = 1;
1549b50d902SRodney W. Grimes 				break;
1559b50d902SRodney W. Grimes 			case 2:
1569b50d902SRodney W. Grimes 				F2->unpair = 1;
1579b50d902SRodney W. Grimes 				break;
1589b50d902SRodney W. Grimes 			default:
1599b50d902SRodney W. Grimes 				errx(1, "-a option file number not 1 or 2");
1609b50d902SRodney W. Grimes 				break;
1619b50d902SRodney W. Grimes 			}
1629b50d902SRodney W. Grimes 			if (*end)
1639b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
1649b50d902SRodney W. Grimes 			break;
1659b50d902SRodney W. Grimes 		case 'e':
1669b50d902SRodney W. Grimes 			empty = optarg;
1679b50d902SRodney W. Grimes 			break;
1689b50d902SRodney W. Grimes 		case 'j':
1699b50d902SRodney W. Grimes 			if ((F1->joinf = F2->joinf =
1709b50d902SRodney W. Grimes 			    strtol(optarg, &end, 10)) < 1)
1719b50d902SRodney W. Grimes 				errx(1, "-j option field number less than 1");
1729b50d902SRodney W. Grimes 			if (*end)
1739b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1749b50d902SRodney W. Grimes 			--F1->joinf;
1759b50d902SRodney W. Grimes 			--F2->joinf;
1769b50d902SRodney W. Grimes 			break;
1779b50d902SRodney W. Grimes 		case 'o':
1789b50d902SRodney W. Grimes 			fieldarg(optarg);
1799b50d902SRodney W. Grimes 			break;
1809b50d902SRodney W. Grimes 		case 't':
1819b50d902SRodney W. Grimes 			spans = 0;
1829b50d902SRodney W. Grimes 			if (strlen(tabchar = optarg) != 1)
1839b50d902SRodney W. Grimes 				errx(1, "illegal tab character specification");
1849b50d902SRodney W. Grimes 			break;
1859b50d902SRodney W. Grimes 		case 'v':
1869b50d902SRodney W. Grimes 			vflag = 1;
1879b50d902SRodney W. Grimes 			joinout = 0;
1889b50d902SRodney W. Grimes 			switch (strtol(optarg, &end, 10)) {
1899b50d902SRodney W. Grimes 			case 1:
1909b50d902SRodney W. Grimes 				F1->unpair = 1;
1919b50d902SRodney W. Grimes 				break;
1929b50d902SRodney W. Grimes 			case 2:
1939b50d902SRodney W. Grimes 				F2->unpair = 1;
1949b50d902SRodney W. Grimes 				break;
1959b50d902SRodney W. Grimes 			default:
1969b50d902SRodney W. Grimes 				errx(1, "-v option file number not 1 or 2");
1979b50d902SRodney W. Grimes 				break;
1989b50d902SRodney W. Grimes 			}
1999b50d902SRodney W. Grimes 			if (*end)
2009b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
2019b50d902SRodney W. Grimes 			break;
2029b50d902SRodney W. Grimes 		case '?':
2039b50d902SRodney W. Grimes 		default:
2049b50d902SRodney W. Grimes 			usage();
2059b50d902SRodney W. Grimes 		}
2069b50d902SRodney W. Grimes 	}
2079b50d902SRodney W. Grimes 	argc -= optind;
2089b50d902SRodney W. Grimes 	argv += optind;
2099b50d902SRodney W. Grimes 
2109b50d902SRodney W. Grimes 	if (aflag && vflag)
2119b50d902SRodney W. Grimes 		errx(1, "the -a and -v options are mutually exclusive");
2129b50d902SRodney W. Grimes 
2139b50d902SRodney W. Grimes 	if (argc != 2)
2149b50d902SRodney W. Grimes 		usage();
2159b50d902SRodney W. Grimes 
2169b50d902SRodney W. Grimes 	/* Open the files; "-" means stdin. */
2179b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2189b50d902SRodney W. Grimes 		F1->fp = stdin;
2199b50d902SRodney W. Grimes 	else if ((F1->fp = fopen(*argv, "r")) == NULL)
2209b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2219b50d902SRodney W. Grimes 	++argv;
2229b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2239b50d902SRodney W. Grimes 		F2->fp = stdin;
2249b50d902SRodney W. Grimes 	else if ((F2->fp = fopen(*argv, "r")) == NULL)
2259b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2269b50d902SRodney W. Grimes 	if (F1->fp == stdin && F2->fp == stdin)
2279b50d902SRodney W. Grimes 		errx(1, "only one input file may be stdin");
2289b50d902SRodney W. Grimes 
2299b50d902SRodney W. Grimes 	slurp(F1);
2309b50d902SRodney W. Grimes 	slurp(F2);
2319b50d902SRodney W. Grimes 	while (F1->setcnt && F2->setcnt) {
2329b50d902SRodney W. Grimes 		cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
2339b50d902SRodney W. Grimes 		if (cval == 0) {
2349b50d902SRodney W. Grimes 			/* Oh joy, oh rapture, oh beauty divine! */
2359b50d902SRodney W. Grimes 			if (joinout)
2369b50d902SRodney W. Grimes 				joinlines(F1, F2);
2379b50d902SRodney W. Grimes 			slurp(F1);
2389b50d902SRodney W. Grimes 			slurp(F2);
2399b50d902SRodney W. Grimes 		} else if (cval < 0) {
2409b50d902SRodney W. Grimes 			/* File 1 takes the lead... */
2419b50d902SRodney W. Grimes 			if (F1->unpair)
2429b50d902SRodney W. Grimes 				joinlines(F1, NULL);
2439b50d902SRodney W. Grimes 			slurp(F1);
2449b50d902SRodney W. Grimes 		} else {
2459b50d902SRodney W. Grimes 			/* File 2 takes the lead... */
2469b50d902SRodney W. Grimes 			if (F2->unpair)
2479b50d902SRodney W. Grimes 				joinlines(F2, NULL);
2489b50d902SRodney W. Grimes 			slurp(F2);
2499b50d902SRodney W. Grimes 		}
2509b50d902SRodney W. Grimes 	}
2519b50d902SRodney W. Grimes 
2529b50d902SRodney W. Grimes 	/*
2539b50d902SRodney W. Grimes 	 * Now that one of the files is used up, optionally output any
2549b50d902SRodney W. Grimes 	 * remaining lines from the other file.
2559b50d902SRodney W. Grimes 	 */
2569b50d902SRodney W. Grimes 	if (F1->unpair)
2579b50d902SRodney W. Grimes 		while (F1->setcnt) {
2589b50d902SRodney W. Grimes 			joinlines(F1, NULL);
2599b50d902SRodney W. Grimes 			slurp(F1);
2609b50d902SRodney W. Grimes 		}
2619b50d902SRodney W. Grimes 	if (F2->unpair)
2629b50d902SRodney W. Grimes 		while (F2->setcnt) {
2639b50d902SRodney W. Grimes 			joinlines(F2, NULL);
2649b50d902SRodney W. Grimes 			slurp(F2);
2659b50d902SRodney W. Grimes 		}
2669b50d902SRodney W. Grimes 	exit(0);
2679b50d902SRodney W. Grimes }
2689b50d902SRodney W. Grimes 
2699b50d902SRodney W. Grimes void
2709b50d902SRodney W. Grimes slurp(F)
2719b50d902SRodney W. Grimes 	INPUT *F;
2729b50d902SRodney W. Grimes {
2739b50d902SRodney W. Grimes 	LINE *lp, *lastlp, tmp;
2749b50d902SRodney W. Grimes 	size_t len;
2759b50d902SRodney W. Grimes 	int cnt;
2769b50d902SRodney W. Grimes 	char *bp, *fieldp;
2779b50d902SRodney W. Grimes 
2789b50d902SRodney W. Grimes 	/*
2799b50d902SRodney W. Grimes 	 * Read all of the lines from an input file that have the same
2809b50d902SRodney W. Grimes 	 * join field.
2819b50d902SRodney W. Grimes 	 */
2829b50d902SRodney W. Grimes 	F->setcnt = 0;
283bf6570c4SPoul-Henning Kamp 	for (lastlp = NULL;; ++F->setcnt) {
2849b50d902SRodney W. Grimes 		/*
2859b50d902SRodney W. Grimes 		 * If we're out of space to hold line structures, allocate
2869b50d902SRodney W. Grimes 		 * more.  Initialize the structure so that we know that this
2879b50d902SRodney W. Grimes 		 * is new space.
2889b50d902SRodney W. Grimes 		 */
2899b50d902SRodney W. Grimes 		if (F->setcnt == F->setalloc) {
2909b50d902SRodney W. Grimes 			cnt = F->setalloc;
2919b50d902SRodney W. Grimes 			F->setalloc += 50;
2929b50d902SRodney W. Grimes 			if ((F->set = realloc(F->set,
2939b50d902SRodney W. Grimes 			    F->setalloc * sizeof(LINE))) == NULL)
2949b50d902SRodney W. Grimes 				err(1, NULL);
2959b50d902SRodney W. Grimes 			memset(F->set + cnt, 0, 50 * sizeof(LINE));
2966f0a860fSPeter Wemm 
2976f0a860fSPeter Wemm 			/* re-set lastlp in case it moved */
2986f0a860fSPeter Wemm 			if (lastlp != NULL)
2996f0a860fSPeter Wemm 				lastlp = &F->set[F->setcnt - 1];
3009b50d902SRodney W. Grimes 		}
3019b50d902SRodney W. Grimes 
3029b50d902SRodney W. Grimes 		/*
3039b50d902SRodney W. Grimes 		 * Get any pushed back line, else get the next line.  Allocate
3049b50d902SRodney W. Grimes 		 * space as necessary.  If taking the line from the stack swap
3059b50d902SRodney W. Grimes 		 * the two structures so that we don't lose space allocated to
3069b50d902SRodney W. Grimes 		 * either structure.  This could be avoided by doing another
3079b50d902SRodney W. Grimes 		 * level of indirection, but it's probably okay as is.
3089b50d902SRodney W. Grimes 		 */
3099b50d902SRodney W. Grimes 		lp = &F->set[F->setcnt];
310bf6570c4SPoul-Henning Kamp 		if (F->setcnt)
311bf6570c4SPoul-Henning Kamp 			lastlp = &F->set[F->setcnt - 1];
3129b50d902SRodney W. Grimes 		if (F->pushbool) {
3139b50d902SRodney W. Grimes 			tmp = F->set[F->setcnt];
3149b50d902SRodney W. Grimes 			F->set[F->setcnt] = F->set[F->pushback];
3159b50d902SRodney W. Grimes 			F->set[F->pushback] = tmp;
3169b50d902SRodney W. Grimes 			F->pushbool = 0;
3179b50d902SRodney W. Grimes 			continue;
3189b50d902SRodney W. Grimes 		}
3199b50d902SRodney W. Grimes 		if ((bp = fgetln(F->fp, &len)) == NULL)
3209b50d902SRodney W. Grimes 			return;
3219b50d902SRodney W. Grimes 		if (lp->linealloc <= len + 1) {
3226f0a860fSPeter Wemm 			lp->linealloc += MAX(100, len + 1 - lp->linealloc);
3239b50d902SRodney W. Grimes 			if ((lp->line =
3249b50d902SRodney W. Grimes 			    realloc(lp->line, lp->linealloc)) == NULL)
3259b50d902SRodney W. Grimes 				err(1, NULL);
3269b50d902SRodney W. Grimes 		}
3279b50d902SRodney W. Grimes 		memmove(lp->line, bp, len);
3289b50d902SRodney W. Grimes 
3299b50d902SRodney W. Grimes 		/* Replace trailing newline, if it exists. */
3309b50d902SRodney W. Grimes 		if (bp[len - 1] == '\n')
3319b50d902SRodney W. Grimes 			lp->line[len - 1] = '\0';
3329b50d902SRodney W. Grimes 		else
3339b50d902SRodney W. Grimes 			lp->line[len] = '\0';
3349b50d902SRodney W. Grimes 		bp = lp->line;
3359b50d902SRodney W. Grimes 
3369b50d902SRodney W. Grimes 		/* Split the line into fields, allocate space as necessary. */
3379b50d902SRodney W. Grimes 		lp->fieldcnt = 0;
3389b50d902SRodney W. Grimes 		while ((fieldp = strsep(&bp, tabchar)) != NULL) {
3399b50d902SRodney W. Grimes 			if (spans && *fieldp == '\0')
3409b50d902SRodney W. Grimes 				continue;
3419b50d902SRodney W. Grimes 			if (lp->fieldcnt == lp->fieldalloc) {
3429b50d902SRodney W. Grimes 				lp->fieldalloc += 50;
3439b50d902SRodney W. Grimes 				if ((lp->fields = realloc(lp->fields,
3449b50d902SRodney W. Grimes 				    lp->fieldalloc * sizeof(char *))) == NULL)
3459b50d902SRodney W. Grimes 					err(1, NULL);
3469b50d902SRodney W. Grimes 			}
3479b50d902SRodney W. Grimes 			lp->fields[lp->fieldcnt++] = fieldp;
3489b50d902SRodney W. Grimes 		}
3499b50d902SRodney W. Grimes 
3509b50d902SRodney W. Grimes 		/* See if the join field value has changed. */
3519b50d902SRodney W. Grimes 		if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
3529b50d902SRodney W. Grimes 			F->pushbool = 1;
3539b50d902SRodney W. Grimes 			F->pushback = F->setcnt;
3549b50d902SRodney W. Grimes 			break;
3559b50d902SRodney W. Grimes 		}
3569b50d902SRodney W. Grimes 	}
3579b50d902SRodney W. Grimes }
3589b50d902SRodney W. Grimes 
3599b50d902SRodney W. Grimes int
3609b50d902SRodney W. Grimes cmp(lp1, fieldno1, lp2, fieldno2)
3619b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
3629b50d902SRodney W. Grimes 	u_long fieldno1, fieldno2;
3639b50d902SRodney W. Grimes {
364cefe4f6fSJoerg Wunsch 	if (lp1->fieldcnt <= fieldno1)
3656f0a860fSPeter Wemm 		return (lp2->fieldcnt <= fieldno2 ? 0 : 1);
366cefe4f6fSJoerg Wunsch 	if (lp2->fieldcnt <= fieldno2)
3679b50d902SRodney W. Grimes 		return (-1);
3689b50d902SRodney W. Grimes 	return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
3699b50d902SRodney W. Grimes }
3709b50d902SRodney W. Grimes 
3719b50d902SRodney W. Grimes void
3729b50d902SRodney W. Grimes joinlines(F1, F2)
3739b50d902SRodney W. Grimes 	INPUT *F1, *F2;
3749b50d902SRodney W. Grimes {
3759b50d902SRodney W. Grimes 	int cnt1, cnt2;
3769b50d902SRodney W. Grimes 
3779b50d902SRodney W. Grimes 	/*
3789b50d902SRodney W. Grimes 	 * Output the results of a join comparison.  The output may be from
3799b50d902SRodney W. Grimes 	 * either file 1 or file 2 (in which case the first argument is the
3809b50d902SRodney W. Grimes 	 * file from which to output) or from both.
3819b50d902SRodney W. Grimes 	 */
3829b50d902SRodney W. Grimes 	if (F2 == NULL) {
3839b50d902SRodney W. Grimes 		for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3849b50d902SRodney W. Grimes 			outoneline(F1, &F1->set[cnt1]);
3859b50d902SRodney W. Grimes 		return;
3869b50d902SRodney W. Grimes 	}
3879b50d902SRodney W. Grimes 	for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3889b50d902SRodney W. Grimes 		for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
3899b50d902SRodney W. Grimes 			outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
3909b50d902SRodney W. Grimes }
3919b50d902SRodney W. Grimes 
3929b50d902SRodney W. Grimes void
3939b50d902SRodney W. Grimes outoneline(F, lp)
3949b50d902SRodney W. Grimes 	INPUT *F;
3959b50d902SRodney W. Grimes 	LINE *lp;
3969b50d902SRodney W. Grimes {
3979b50d902SRodney W. Grimes 	int cnt;
3989b50d902SRodney W. Grimes 
3999b50d902SRodney W. Grimes 	/*
4009b50d902SRodney W. Grimes 	 * Output a single line from one of the files, according to the
4019b50d902SRodney W. Grimes 	 * join rules.  This happens when we are writing unmatched single
4029b50d902SRodney W. Grimes 	 * lines.  Output empty fields in the right places.
4039b50d902SRodney W. Grimes 	 */
4049b50d902SRodney W. Grimes 	if (olist)
4059b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt) {
4069b50d902SRodney W. Grimes 			if (olist[cnt].filenum == F->number)
4079b50d902SRodney W. Grimes 				outfield(lp, olist[cnt].fieldno, 0);
4089b50d902SRodney W. Grimes 			else
4099b50d902SRodney W. Grimes 				outfield(lp, 0, 1);
4109b50d902SRodney W. Grimes 		}
4119b50d902SRodney W. Grimes 	else
4129b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
4139b50d902SRodney W. Grimes 			outfield(lp, cnt, 0);
4149b50d902SRodney W. Grimes 	(void)printf("\n");
4159b50d902SRodney W. Grimes 	if (ferror(stdout))
4169b50d902SRodney W. Grimes 		err(1, "stdout");
4179b50d902SRodney W. Grimes 	needsep = 0;
4189b50d902SRodney W. Grimes }
4199b50d902SRodney W. Grimes 
4209b50d902SRodney W. Grimes void
4219b50d902SRodney W. Grimes outtwoline(F1, lp1, F2, lp2)
4229b50d902SRodney W. Grimes 	INPUT *F1, *F2;
4239b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
4249b50d902SRodney W. Grimes {
4259b50d902SRodney W. Grimes 	int cnt;
4269b50d902SRodney W. Grimes 
4279b50d902SRodney W. Grimes 	/* Output a pair of lines according to the join list (if any). */
4289b50d902SRodney W. Grimes 	if (olist)
4299b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt)
4309b50d902SRodney W. Grimes 			if (olist[cnt].filenum == 1)
4319b50d902SRodney W. Grimes 				outfield(lp1, olist[cnt].fieldno, 0);
4329b50d902SRodney W. Grimes 			else /* if (olist[cnt].filenum == 2) */
4339b50d902SRodney W. Grimes 				outfield(lp2, olist[cnt].fieldno, 0);
4349b50d902SRodney W. Grimes 	else {
4359b50d902SRodney W. Grimes 		/*
4369b50d902SRodney W. Grimes 		 * Output the join field, then the remaining fields from F1
4379b50d902SRodney W. Grimes 		 * and F2.
4389b50d902SRodney W. Grimes 		 */
4399b50d902SRodney W. Grimes 		outfield(lp1, F1->joinf, 0);
4409b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
4419b50d902SRodney W. Grimes 			if (F1->joinf != cnt)
4429b50d902SRodney W. Grimes 				outfield(lp1, cnt, 0);
4439b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
4449b50d902SRodney W. Grimes 			if (F2->joinf != cnt)
4459b50d902SRodney W. Grimes 				outfield(lp2, cnt, 0);
4469b50d902SRodney W. Grimes 	}
4479b50d902SRodney W. Grimes 	(void)printf("\n");
4489b50d902SRodney W. Grimes 	if (ferror(stdout))
4499b50d902SRodney W. Grimes 		err(1, "stdout");
4509b50d902SRodney W. Grimes 	needsep = 0;
4519b50d902SRodney W. Grimes }
4529b50d902SRodney W. Grimes 
4539b50d902SRodney W. Grimes void
4549b50d902SRodney W. Grimes outfield(lp, fieldno, out_empty)
4559b50d902SRodney W. Grimes 	LINE *lp;
4569b50d902SRodney W. Grimes 	u_long fieldno;
4579b50d902SRodney W. Grimes 	int out_empty;
4589b50d902SRodney W. Grimes {
4599b50d902SRodney W. Grimes 	if (needsep++)
4609b50d902SRodney W. Grimes 		(void)printf("%c", *tabchar);
4619ef5c48bSBill Fumerola 	if (!ferror(stdout)) {
462cefe4f6fSJoerg Wunsch 		if (lp->fieldcnt <= fieldno || out_empty) {
4639b50d902SRodney W. Grimes 			if (empty != NULL)
4649b50d902SRodney W. Grimes 				(void)printf("%s", empty);
4659b50d902SRodney W. Grimes 		} else {
4669b50d902SRodney W. Grimes 			if (*lp->fields[fieldno] == '\0')
4679b50d902SRodney W. Grimes 				return;
4689b50d902SRodney W. Grimes 			(void)printf("%s", lp->fields[fieldno]);
4699b50d902SRodney W. Grimes 		}
4709ef5c48bSBill Fumerola 	}
4719b50d902SRodney W. Grimes 	if (ferror(stdout))
4729b50d902SRodney W. Grimes 		err(1, "stdout");
4739b50d902SRodney W. Grimes }
4749b50d902SRodney W. Grimes 
4759b50d902SRodney W. Grimes /*
4769b50d902SRodney W. Grimes  * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
4779b50d902SRodney W. Grimes  * fields.
4789b50d902SRodney W. Grimes  */
4799b50d902SRodney W. Grimes void
4809b50d902SRodney W. Grimes fieldarg(option)
4819b50d902SRodney W. Grimes 	char *option;
4829b50d902SRodney W. Grimes {
4839b50d902SRodney W. Grimes 	u_long fieldno;
4849b50d902SRodney W. Grimes 	char *end, *token;
4859b50d902SRodney W. Grimes 
4866f0a860fSPeter Wemm 	while ((token = strsep(&option, ", \t")) != NULL) {
4879b50d902SRodney W. Grimes 		if (*token == '\0')
4889b50d902SRodney W. Grimes 			continue;
4899b50d902SRodney W. Grimes 		if (token[0] != '1' && token[0] != '2' || token[1] != '.')
4909b50d902SRodney W. Grimes 			errx(1, "malformed -o option field");
4919b50d902SRodney W. Grimes 		fieldno = strtol(token + 2, &end, 10);
4929b50d902SRodney W. Grimes 		if (*end)
4939b50d902SRodney W. Grimes 			errx(1, "malformed -o option field");
4949b50d902SRodney W. Grimes 		if (fieldno == 0)
4959b50d902SRodney W. Grimes 			errx(1, "field numbers are 1 based");
4969b50d902SRodney W. Grimes 		if (olistcnt == olistalloc) {
4979b50d902SRodney W. Grimes 			olistalloc += 50;
4989b50d902SRodney W. Grimes 			if ((olist = realloc(olist,
4999b50d902SRodney W. Grimes 			    olistalloc * sizeof(OLIST))) == NULL)
5009b50d902SRodney W. Grimes 				err(1, NULL);
5019b50d902SRodney W. Grimes 		}
5029b50d902SRodney W. Grimes 		olist[olistcnt].filenum = token[0] - '0';
5039b50d902SRodney W. Grimes 		olist[olistcnt].fieldno = fieldno - 1;
5049b50d902SRodney W. Grimes 		++olistcnt;
5059b50d902SRodney W. Grimes 	}
5069b50d902SRodney W. Grimes }
5079b50d902SRodney W. Grimes 
5089b50d902SRodney W. Grimes void
5099b50d902SRodney W. Grimes obsolete(argv)
5109b50d902SRodney W. Grimes 	char **argv;
5119b50d902SRodney W. Grimes {
5129b50d902SRodney W. Grimes 	int len;
5139b50d902SRodney W. Grimes 	char **p, *ap, *t;
5149b50d902SRodney W. Grimes 
5159b50d902SRodney W. Grimes 	while ((ap = *++argv) != NULL) {
5169b50d902SRodney W. Grimes 		/* Return if "--". */
5179b50d902SRodney W. Grimes 		if (ap[0] == '-' && ap[1] == '-')
5189b50d902SRodney W. Grimes 			return;
519faba086bSJonathan Lemon 		/* skip if not an option */
520faba086bSJonathan Lemon 		if (ap[0] != '-')
521faba086bSJonathan Lemon 			continue;
5229b50d902SRodney W. Grimes 		switch (ap[1]) {
5239b50d902SRodney W. Grimes 		case 'a':
5249b50d902SRodney W. Grimes 			/*
5259b50d902SRodney W. Grimes 			 * The original join allowed "-a", which meant the
5269b50d902SRodney W. Grimes 			 * same as -a1 plus -a2.  POSIX 1003.2, Draft 11.2
5279b50d902SRodney W. Grimes 			 * only specifies this as "-a 1" and "a -2", so we
5289b50d902SRodney W. Grimes 			 * have to use another option flag, one that is
5299b50d902SRodney W. Grimes 			 * unlikely to ever be used or accidentally entered
5309b50d902SRodney W. Grimes 			 * on the command line.  (Well, we could reallocate
5319b50d902SRodney W. Grimes 			 * the argv array, but that hardly seems worthwhile.)
5329b50d902SRodney W. Grimes 			 */
5339b50d902SRodney W. Grimes 			if (ap[2] == '\0')
5349b50d902SRodney W. Grimes 				ap[1] = '\01';
5359b50d902SRodney W. Grimes 			break;
5369b50d902SRodney W. Grimes 		case 'j':
5379b50d902SRodney W. Grimes 			/*
5389b50d902SRodney W. Grimes 			 * The original join allowed "-j[12] arg" and "-j arg".
5399b50d902SRodney W. Grimes 			 * Convert the former to "-[12] arg".  Don't convert
5409b50d902SRodney W. Grimes 			 * the latter since getopt(3) can handle it.
5419b50d902SRodney W. Grimes 			 */
5429b50d902SRodney W. Grimes 			switch(ap[2]) {
5439b50d902SRodney W. Grimes 			case '1':
5449b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5459b50d902SRodney W. Grimes 					goto jbad;
5469b50d902SRodney W. Grimes 				ap[1] = '1';
5479b50d902SRodney W. Grimes 				ap[2] = '\0';
5489b50d902SRodney W. Grimes 				break;
5499b50d902SRodney W. Grimes 			case '2':
5509b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5519b50d902SRodney W. Grimes 					goto jbad;
5529b50d902SRodney W. Grimes 				ap[1] = '2';
5539b50d902SRodney W. Grimes 				ap[2] = '\0';
5549b50d902SRodney W. Grimes 				break;
5559b50d902SRodney W. Grimes 			case '\0':
5569b50d902SRodney W. Grimes 				break;
5579b50d902SRodney W. Grimes 			default:
5589b50d902SRodney W. Grimes jbad:				errx(1, "illegal option -- %s", ap);
5599b50d902SRodney W. Grimes 				usage();
5609b50d902SRodney W. Grimes 			}
5619b50d902SRodney W. Grimes 			break;
5629b50d902SRodney W. Grimes 		case 'o':
5639b50d902SRodney W. Grimes 			/*
5649b50d902SRodney W. Grimes 			 * The original join allowed "-o arg arg".
5659b50d902SRodney W. Grimes 			 * Convert to "-o arg -o arg".
5669b50d902SRodney W. Grimes 			 */
5679b50d902SRodney W. Grimes 			if (ap[2] != '\0')
5689b50d902SRodney W. Grimes 				break;
5699b50d902SRodney W. Grimes 			for (p = argv + 2; *p; ++p) {
5709b50d902SRodney W. Grimes 				if (p[0][0] != '1' &&
5719b50d902SRodney W. Grimes 				    p[0][0] != '2' || p[0][1] != '.')
5729b50d902SRodney W. Grimes 					break;
5739b50d902SRodney W. Grimes 				len = strlen(*p);
5749b50d902SRodney W. Grimes 				if (len - 2 != strspn(*p + 2, "0123456789"))
5759b50d902SRodney W. Grimes 					break;
5769b50d902SRodney W. Grimes 				if ((t = malloc(len + 3)) == NULL)
5779b50d902SRodney W. Grimes 					err(1, NULL);
5789b50d902SRodney W. Grimes 				t[0] = '-';
5799b50d902SRodney W. Grimes 				t[1] = 'o';
5809b50d902SRodney W. Grimes 				memmove(t + 2, *p, len + 1);
5819b50d902SRodney W. Grimes 				*p = t;
5829b50d902SRodney W. Grimes 			}
5839b50d902SRodney W. Grimes 			argv = p - 1;
5849b50d902SRodney W. Grimes 			break;
5859b50d902SRodney W. Grimes 		}
5869b50d902SRodney W. Grimes 	}
5879b50d902SRodney W. Grimes }
5889b50d902SRodney W. Grimes 
5899b50d902SRodney W. Grimes void
5909b50d902SRodney W. Grimes usage()
5919b50d902SRodney W. Grimes {
59221a3d165SPhilippe Charnier 	(void)fprintf(stderr, "%s %s\n%s\n",
5939b50d902SRodney W. Grimes 	    "usage: join [-a fileno | -v fileno ] [-e string] [-1 field]",
59421a3d165SPhilippe Charnier 	    "[-2 field]",
59521a3d165SPhilippe Charnier 		"            [-o list] [-t char] file1 file2");
5969b50d902SRodney W. Grimes 	exit(1);
5979b50d902SRodney W. Grimes }
598