xref: /freebsd/usr.bin/join/join.c (revision bf6570c4a5b48103ded727a6e1e978a1aba20f4e)
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
399b50d902SRodney W. Grimes static 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
459b50d902SRodney W. Grimes static char sccsid[] = "@(#)join.c	8.3 (Berkeley) 4/16/94";
469b50d902SRodney W. Grimes #endif /* not lint */
479b50d902SRodney W. Grimes 
489b50d902SRodney W. Grimes #include <sys/param.h>
499b50d902SRodney W. Grimes 
509b50d902SRodney W. Grimes #include <ctype.h>
519b50d902SRodney W. Grimes #include <err.h>
529b50d902SRodney W. Grimes #include <errno.h>
539b50d902SRodney W. Grimes #include <stdio.h>
549b50d902SRodney W. Grimes #include <stdlib.h>
559b50d902SRodney W. Grimes #include <string.h>
569b50d902SRodney W. Grimes 
579b50d902SRodney W. Grimes /*
589b50d902SRodney W. Grimes  * There's a structure per input file which encapsulates the state of the
599b50d902SRodney W. Grimes  * file.  We repeatedly read lines from each file until we've read in all
609b50d902SRodney W. Grimes  * the consecutive lines from the file with a common join field.  Then we
619b50d902SRodney W. Grimes  * compare the set of lines with an equivalent set from the other file.
629b50d902SRodney W. Grimes  */
639b50d902SRodney W. Grimes typedef struct {
649b50d902SRodney W. Grimes 	char *line;		/* line */
659b50d902SRodney W. Grimes 	u_long linealloc;	/* line allocated count */
669b50d902SRodney W. Grimes 	char **fields;		/* line field(s) */
679b50d902SRodney W. Grimes 	u_long fieldcnt;	/* line field(s) count */
689b50d902SRodney W. Grimes 	u_long fieldalloc;	/* line field(s) allocated count */
699b50d902SRodney W. Grimes } LINE;
709b50d902SRodney W. Grimes 
719b50d902SRodney W. Grimes typedef struct {
729b50d902SRodney W. Grimes 	FILE *fp;		/* file descriptor */
739b50d902SRodney W. Grimes 	u_long joinf;		/* join field (-1, -2, -j) */
749b50d902SRodney W. Grimes 	int unpair;		/* output unpairable lines (-a) */
759b50d902SRodney W. Grimes 	int number;		/* 1 for file 1, 2 for file 2 */
769b50d902SRodney W. Grimes 
779b50d902SRodney W. Grimes 	LINE *set;		/* set of lines with same field */
789b50d902SRodney W. Grimes 	int pushbool;		/* if pushback is set */
799b50d902SRodney W. Grimes 	u_long pushback;	/* line on the stack */
809b50d902SRodney W. Grimes 	u_long setcnt;		/* set count */
819b50d902SRodney W. Grimes 	u_long setalloc;	/* set allocated count */
829b50d902SRodney W. Grimes } INPUT;
839b50d902SRodney W. Grimes INPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, },
849b50d902SRodney W. Grimes       input2 = { NULL, 0, 0, 2, NULL, 0, 0, 0, };
859b50d902SRodney W. Grimes 
869b50d902SRodney W. Grimes typedef struct {
879b50d902SRodney W. Grimes 	u_long	filenum;	/* file number */
889b50d902SRodney W. Grimes 	u_long	fieldno;	/* field number */
899b50d902SRodney W. Grimes } OLIST;
909b50d902SRodney W. Grimes OLIST *olist;			/* output field list */
919b50d902SRodney W. Grimes u_long olistcnt;		/* output field list count */
929b50d902SRodney W. Grimes u_long olistalloc;		/* output field allocated count */
939b50d902SRodney W. Grimes 
949b50d902SRodney W. Grimes int joinout = 1;		/* show lines with matched join fields (-v) */
959b50d902SRodney W. Grimes int needsep;			/* need separator character */
969b50d902SRodney W. Grimes int spans = 1;			/* span multiple delimiters (-t) */
979b50d902SRodney W. Grimes char *empty;			/* empty field replacement string (-e) */
989b50d902SRodney W. Grimes char *tabchar = " \t";		/* delimiter characters (-t) */
999b50d902SRodney W. Grimes 
1009b50d902SRodney W. Grimes int  cmp __P((LINE *, u_long, LINE *, u_long));
1019b50d902SRodney W. Grimes void fieldarg __P((char *));
1029b50d902SRodney W. Grimes void joinlines __P((INPUT *, INPUT *));
1039b50d902SRodney W. Grimes void obsolete __P((char **));
1049b50d902SRodney W. Grimes void outfield __P((LINE *, u_long, int));
1059b50d902SRodney W. Grimes void outoneline __P((INPUT *, LINE *));
1069b50d902SRodney W. Grimes void outtwoline __P((INPUT *, LINE *, INPUT *, LINE *));
1079b50d902SRodney W. Grimes void slurp __P((INPUT *));
1089b50d902SRodney W. Grimes void usage __P((void));
1099b50d902SRodney W. Grimes 
1109b50d902SRodney W. Grimes int
1119b50d902SRodney W. Grimes main(argc, argv)
1129b50d902SRodney W. Grimes 	int argc;
1139b50d902SRodney W. Grimes 	char *argv[];
1149b50d902SRodney W. Grimes {
1159b50d902SRodney W. Grimes 	INPUT *F1, *F2;
1169b50d902SRodney W. Grimes 	int aflag, ch, cval, vflag;
1179b50d902SRodney W. Grimes 	char *end;
1189b50d902SRodney W. Grimes 
1199b50d902SRodney W. Grimes 	F1 = &input1;
1209b50d902SRodney W. Grimes 	F2 = &input2;
1219b50d902SRodney W. Grimes 
1229b50d902SRodney W. Grimes 	aflag = vflag = 0;
1239b50d902SRodney W. Grimes 	obsolete(argv);
1249b50d902SRodney W. Grimes 	while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != EOF) {
1259b50d902SRodney W. Grimes 		switch (ch) {
1269b50d902SRodney W. Grimes 		case '\01':		/* See comment in obsolete(). */
1279b50d902SRodney W. Grimes 			aflag = 1;
1289b50d902SRodney W. Grimes 			F1->unpair = F2->unpair = 1;
1299b50d902SRodney W. Grimes 			break;
1309b50d902SRodney W. Grimes 		case '1':
1319b50d902SRodney W. Grimes 			if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
1329b50d902SRodney W. Grimes 				errx(1, "-1 option field number less than 1");
1339b50d902SRodney W. Grimes 			if (*end)
1349b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1359b50d902SRodney W. Grimes 			--F1->joinf;
1369b50d902SRodney W. Grimes 			break;
1379b50d902SRodney W. Grimes 		case '2':
1389b50d902SRodney W. Grimes 			if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
1399b50d902SRodney W. Grimes 				errx(1, "-2 option field number less than 1");
1409b50d902SRodney W. Grimes 			if (*end)
1419b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1429b50d902SRodney W. Grimes 			--F2->joinf;
1439b50d902SRodney W. Grimes 			break;
1449b50d902SRodney W. Grimes 		case 'a':
1459b50d902SRodney W. Grimes 			aflag = 1;
1469b50d902SRodney W. Grimes 			switch(strtol(optarg, &end, 10)) {
1479b50d902SRodney W. Grimes 			case 1:
1489b50d902SRodney W. Grimes 				F1->unpair = 1;
1499b50d902SRodney W. Grimes 				break;
1509b50d902SRodney W. Grimes 			case 2:
1519b50d902SRodney W. Grimes 				F2->unpair = 1;
1529b50d902SRodney W. Grimes 				break;
1539b50d902SRodney W. Grimes 			default:
1549b50d902SRodney W. Grimes 				errx(1, "-a option file number not 1 or 2");
1559b50d902SRodney W. Grimes 				break;
1569b50d902SRodney W. Grimes 			}
1579b50d902SRodney W. Grimes 			if (*end)
1589b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
1599b50d902SRodney W. Grimes 			break;
1609b50d902SRodney W. Grimes 		case 'e':
1619b50d902SRodney W. Grimes 			empty = optarg;
1629b50d902SRodney W. Grimes 			break;
1639b50d902SRodney W. Grimes 		case 'j':
1649b50d902SRodney W. Grimes 			if ((F1->joinf = F2->joinf =
1659b50d902SRodney W. Grimes 			    strtol(optarg, &end, 10)) < 1)
1669b50d902SRodney W. Grimes 				errx(1, "-j option field number less than 1");
1679b50d902SRodney W. Grimes 			if (*end)
1689b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1699b50d902SRodney W. Grimes 			--F1->joinf;
1709b50d902SRodney W. Grimes 			--F2->joinf;
1719b50d902SRodney W. Grimes 			break;
1729b50d902SRodney W. Grimes 		case 'o':
1739b50d902SRodney W. Grimes 			fieldarg(optarg);
1749b50d902SRodney W. Grimes 			break;
1759b50d902SRodney W. Grimes 		case 't':
1769b50d902SRodney W. Grimes 			spans = 0;
1779b50d902SRodney W. Grimes 			if (strlen(tabchar = optarg) != 1)
1789b50d902SRodney W. Grimes 				errx(1, "illegal tab character specification");
1799b50d902SRodney W. Grimes 			break;
1809b50d902SRodney W. Grimes 		case 'v':
1819b50d902SRodney W. Grimes 			vflag = 1;
1829b50d902SRodney W. Grimes 			joinout = 0;
1839b50d902SRodney W. Grimes 			switch (strtol(optarg, &end, 10)) {
1849b50d902SRodney W. Grimes 			case 1:
1859b50d902SRodney W. Grimes 				F1->unpair = 1;
1869b50d902SRodney W. Grimes 				break;
1879b50d902SRodney W. Grimes 			case 2:
1889b50d902SRodney W. Grimes 				F2->unpair = 1;
1899b50d902SRodney W. Grimes 				break;
1909b50d902SRodney W. Grimes 			default:
1919b50d902SRodney W. Grimes 				errx(1, "-v option file number not 1 or 2");
1929b50d902SRodney W. Grimes 				break;
1939b50d902SRodney W. Grimes 			}
1949b50d902SRodney W. Grimes 			if (*end)
1959b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
1969b50d902SRodney W. Grimes 			break;
1979b50d902SRodney W. Grimes 		case '?':
1989b50d902SRodney W. Grimes 		default:
1999b50d902SRodney W. Grimes 			usage();
2009b50d902SRodney W. Grimes 		}
2019b50d902SRodney W. Grimes 	}
2029b50d902SRodney W. Grimes 	argc -= optind;
2039b50d902SRodney W. Grimes 	argv += optind;
2049b50d902SRodney W. Grimes 
2059b50d902SRodney W. Grimes 	if (aflag && vflag)
2069b50d902SRodney W. Grimes 		errx(1, "the -a and -v options are mutually exclusive");
2079b50d902SRodney W. Grimes 
2089b50d902SRodney W. Grimes 	if (argc != 2)
2099b50d902SRodney W. Grimes 		usage();
2109b50d902SRodney W. Grimes 
2119b50d902SRodney W. Grimes 	/* Open the files; "-" means stdin. */
2129b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2139b50d902SRodney W. Grimes 		F1->fp = stdin;
2149b50d902SRodney W. Grimes 	else if ((F1->fp = fopen(*argv, "r")) == NULL)
2159b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2169b50d902SRodney W. Grimes 	++argv;
2179b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2189b50d902SRodney W. Grimes 		F2->fp = stdin;
2199b50d902SRodney W. Grimes 	else if ((F2->fp = fopen(*argv, "r")) == NULL)
2209b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2219b50d902SRodney W. Grimes 	if (F1->fp == stdin && F2->fp == stdin)
2229b50d902SRodney W. Grimes 		errx(1, "only one input file may be stdin");
2239b50d902SRodney W. Grimes 
2249b50d902SRodney W. Grimes 	slurp(F1);
2259b50d902SRodney W. Grimes 	slurp(F2);
2269b50d902SRodney W. Grimes 	while (F1->setcnt && F2->setcnt) {
2279b50d902SRodney W. Grimes 		cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
2289b50d902SRodney W. Grimes 		if (cval == 0) {
2299b50d902SRodney W. Grimes 			/* Oh joy, oh rapture, oh beauty divine! */
2309b50d902SRodney W. Grimes 			if (joinout)
2319b50d902SRodney W. Grimes 				joinlines(F1, F2);
2329b50d902SRodney W. Grimes 			slurp(F1);
2339b50d902SRodney W. Grimes 			slurp(F2);
2349b50d902SRodney W. Grimes 		} else if (cval < 0) {
2359b50d902SRodney W. Grimes 			/* File 1 takes the lead... */
2369b50d902SRodney W. Grimes 			if (F1->unpair)
2379b50d902SRodney W. Grimes 				joinlines(F1, NULL);
2389b50d902SRodney W. Grimes 			slurp(F1);
2399b50d902SRodney W. Grimes 		} else {
2409b50d902SRodney W. Grimes 			/* File 2 takes the lead... */
2419b50d902SRodney W. Grimes 			if (F2->unpair)
2429b50d902SRodney W. Grimes 				joinlines(F2, NULL);
2439b50d902SRodney W. Grimes 			slurp(F2);
2449b50d902SRodney W. Grimes 		}
2459b50d902SRodney W. Grimes 	}
2469b50d902SRodney W. Grimes 
2479b50d902SRodney W. Grimes 	/*
2489b50d902SRodney W. Grimes 	 * Now that one of the files is used up, optionally output any
2499b50d902SRodney W. Grimes 	 * remaining lines from the other file.
2509b50d902SRodney W. Grimes 	 */
2519b50d902SRodney W. Grimes 	if (F1->unpair)
2529b50d902SRodney W. Grimes 		while (F1->setcnt) {
2539b50d902SRodney W. Grimes 			joinlines(F1, NULL);
2549b50d902SRodney W. Grimes 			slurp(F1);
2559b50d902SRodney W. Grimes 		}
2569b50d902SRodney W. Grimes 	if (F2->unpair)
2579b50d902SRodney W. Grimes 		while (F2->setcnt) {
2589b50d902SRodney W. Grimes 			joinlines(F2, NULL);
2599b50d902SRodney W. Grimes 			slurp(F2);
2609b50d902SRodney W. Grimes 		}
2619b50d902SRodney W. Grimes 	exit(0);
2629b50d902SRodney W. Grimes }
2639b50d902SRodney W. Grimes 
2649b50d902SRodney W. Grimes void
2659b50d902SRodney W. Grimes slurp(F)
2669b50d902SRodney W. Grimes 	INPUT *F;
2679b50d902SRodney W. Grimes {
2689b50d902SRodney W. Grimes 	LINE *lp, *lastlp, tmp;
2699b50d902SRodney W. Grimes 	size_t len;
2709b50d902SRodney W. Grimes 	int cnt;
2719b50d902SRodney W. Grimes 	char *bp, *fieldp;
2729b50d902SRodney W. Grimes 
2739b50d902SRodney W. Grimes 	/*
2749b50d902SRodney W. Grimes 	 * Read all of the lines from an input file that have the same
2759b50d902SRodney W. Grimes 	 * join field.
2769b50d902SRodney W. Grimes 	 */
2779b50d902SRodney W. Grimes 	F->setcnt = 0;
278bf6570c4SPoul-Henning Kamp 	for (lastlp = NULL;; ++F->setcnt) {
2799b50d902SRodney W. Grimes 		/*
2809b50d902SRodney W. Grimes 		 * If we're out of space to hold line structures, allocate
2819b50d902SRodney W. Grimes 		 * more.  Initialize the structure so that we know that this
2829b50d902SRodney W. Grimes 		 * is new space.
2839b50d902SRodney W. Grimes 		 */
2849b50d902SRodney W. Grimes 		if (F->setcnt == F->setalloc) {
2859b50d902SRodney W. Grimes 			cnt = F->setalloc;
2869b50d902SRodney W. Grimes 			F->setalloc += 50;
2879b50d902SRodney W. Grimes 			if ((F->set = realloc(F->set,
2889b50d902SRodney W. Grimes 			    F->setalloc * sizeof(LINE))) == NULL)
2899b50d902SRodney W. Grimes 				err(1, NULL);
2909b50d902SRodney W. Grimes 			memset(F->set + cnt, 0, 50 * sizeof(LINE));
2919b50d902SRodney W. Grimes 		}
2929b50d902SRodney W. Grimes 
2939b50d902SRodney W. Grimes 		/*
2949b50d902SRodney W. Grimes 		 * Get any pushed back line, else get the next line.  Allocate
2959b50d902SRodney W. Grimes 		 * space as necessary.  If taking the line from the stack swap
2969b50d902SRodney W. Grimes 		 * the two structures so that we don't lose space allocated to
2979b50d902SRodney W. Grimes 		 * either structure.  This could be avoided by doing another
2989b50d902SRodney W. Grimes 		 * level of indirection, but it's probably okay as is.
2999b50d902SRodney W. Grimes 		 */
3009b50d902SRodney W. Grimes 		lp = &F->set[F->setcnt];
301bf6570c4SPoul-Henning Kamp 		if (F->setcnt)
302bf6570c4SPoul-Henning Kamp 			lastlp = &F->set[F->setcnt - 1];
3039b50d902SRodney W. Grimes 		if (F->pushbool) {
3049b50d902SRodney W. Grimes 			tmp = F->set[F->setcnt];
3059b50d902SRodney W. Grimes 			F->set[F->setcnt] = F->set[F->pushback];
3069b50d902SRodney W. Grimes 			F->set[F->pushback] = tmp;
3079b50d902SRodney W. Grimes 			F->pushbool = 0;
3089b50d902SRodney W. Grimes 			continue;
3099b50d902SRodney W. Grimes 		}
3109b50d902SRodney W. Grimes 		if ((bp = fgetln(F->fp, &len)) == NULL)
3119b50d902SRodney W. Grimes 			return;
3129b50d902SRodney W. Grimes 		if (lp->linealloc <= len + 1) {
3139b50d902SRodney W. Grimes 			lp->linealloc += MAX(100, len + 1);
3149b50d902SRodney W. Grimes 			if ((lp->line =
3159b50d902SRodney W. Grimes 			    realloc(lp->line, lp->linealloc)) == NULL)
3169b50d902SRodney W. Grimes 				err(1, NULL);
3179b50d902SRodney W. Grimes 		}
3189b50d902SRodney W. Grimes 		memmove(lp->line, bp, len);
3199b50d902SRodney W. Grimes 
3209b50d902SRodney W. Grimes 		/* Replace trailing newline, if it exists. */
3219b50d902SRodney W. Grimes 		if (bp[len - 1] == '\n')
3229b50d902SRodney W. Grimes 			lp->line[len - 1] = '\0';
3239b50d902SRodney W. Grimes 		else
3249b50d902SRodney W. Grimes 			lp->line[len] = '\0';
3259b50d902SRodney W. Grimes 		bp = lp->line;
3269b50d902SRodney W. Grimes 
3279b50d902SRodney W. Grimes 		/* Split the line into fields, allocate space as necessary. */
3289b50d902SRodney W. Grimes 		lp->fieldcnt = 0;
3299b50d902SRodney W. Grimes 		while ((fieldp = strsep(&bp, tabchar)) != NULL) {
3309b50d902SRodney W. Grimes 			if (spans && *fieldp == '\0')
3319b50d902SRodney W. Grimes 				continue;
3329b50d902SRodney W. Grimes 			if (lp->fieldcnt == lp->fieldalloc) {
3339b50d902SRodney W. Grimes 				lp->fieldalloc += 50;
3349b50d902SRodney W. Grimes 				if ((lp->fields = realloc(lp->fields,
3359b50d902SRodney W. Grimes 				    lp->fieldalloc * sizeof(char *))) == NULL)
3369b50d902SRodney W. Grimes 					err(1, NULL);
3379b50d902SRodney W. Grimes 			}
3389b50d902SRodney W. Grimes 			lp->fields[lp->fieldcnt++] = fieldp;
3399b50d902SRodney W. Grimes 		}
3409b50d902SRodney W. Grimes 
3419b50d902SRodney W. Grimes 		/* See if the join field value has changed. */
3429b50d902SRodney W. Grimes 		if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
3439b50d902SRodney W. Grimes 			F->pushbool = 1;
3449b50d902SRodney W. Grimes 			F->pushback = F->setcnt;
3459b50d902SRodney W. Grimes 			break;
3469b50d902SRodney W. Grimes 		}
3479b50d902SRodney W. Grimes 	}
3489b50d902SRodney W. Grimes }
3499b50d902SRodney W. Grimes 
3509b50d902SRodney W. Grimes int
3519b50d902SRodney W. Grimes cmp(lp1, fieldno1, lp2, fieldno2)
3529b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
3539b50d902SRodney W. Grimes 	u_long fieldno1, fieldno2;
3549b50d902SRodney W. Grimes {
3559b50d902SRodney W. Grimes 	if (lp1->fieldcnt < fieldno1)
3569b50d902SRodney W. Grimes 		return (lp2->fieldcnt < fieldno2 ? 0 : 1);
3579b50d902SRodney W. Grimes 	if (lp2->fieldcnt < fieldno2)
3589b50d902SRodney W. Grimes 		return (-1);
3599b50d902SRodney W. Grimes 	return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2]));
3609b50d902SRodney W. Grimes }
3619b50d902SRodney W. Grimes 
3629b50d902SRodney W. Grimes void
3639b50d902SRodney W. Grimes joinlines(F1, F2)
3649b50d902SRodney W. Grimes 	INPUT *F1, *F2;
3659b50d902SRodney W. Grimes {
3669b50d902SRodney W. Grimes 	int cnt1, cnt2;
3679b50d902SRodney W. Grimes 
3689b50d902SRodney W. Grimes 	/*
3699b50d902SRodney W. Grimes 	 * Output the results of a join comparison.  The output may be from
3709b50d902SRodney W. Grimes 	 * either file 1 or file 2 (in which case the first argument is the
3719b50d902SRodney W. Grimes 	 * file from which to output) or from both.
3729b50d902SRodney W. Grimes 	 */
3739b50d902SRodney W. Grimes 	if (F2 == NULL) {
3749b50d902SRodney W. Grimes 		for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3759b50d902SRodney W. Grimes 			outoneline(F1, &F1->set[cnt1]);
3769b50d902SRodney W. Grimes 		return;
3779b50d902SRodney W. Grimes 	}
3789b50d902SRodney W. Grimes 	for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3799b50d902SRodney W. Grimes 		for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
3809b50d902SRodney W. Grimes 			outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
3819b50d902SRodney W. Grimes }
3829b50d902SRodney W. Grimes 
3839b50d902SRodney W. Grimes void
3849b50d902SRodney W. Grimes outoneline(F, lp)
3859b50d902SRodney W. Grimes 	INPUT *F;
3869b50d902SRodney W. Grimes 	LINE *lp;
3879b50d902SRodney W. Grimes {
3889b50d902SRodney W. Grimes 	int cnt;
3899b50d902SRodney W. Grimes 
3909b50d902SRodney W. Grimes 	/*
3919b50d902SRodney W. Grimes 	 * Output a single line from one of the files, according to the
3929b50d902SRodney W. Grimes 	 * join rules.  This happens when we are writing unmatched single
3939b50d902SRodney W. Grimes 	 * lines.  Output empty fields in the right places.
3949b50d902SRodney W. Grimes 	 */
3959b50d902SRodney W. Grimes 	if (olist)
3969b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt) {
3979b50d902SRodney W. Grimes 			if (olist[cnt].filenum == F->number)
3989b50d902SRodney W. Grimes 				outfield(lp, olist[cnt].fieldno, 0);
3999b50d902SRodney W. Grimes 			else
4009b50d902SRodney W. Grimes 				outfield(lp, 0, 1);
4019b50d902SRodney W. Grimes 		}
4029b50d902SRodney W. Grimes 	else
4039b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
4049b50d902SRodney W. Grimes 			outfield(lp, cnt, 0);
4059b50d902SRodney W. Grimes 	(void)printf("\n");
4069b50d902SRodney W. Grimes 	if (ferror(stdout))
4079b50d902SRodney W. Grimes 		err(1, "stdout");
4089b50d902SRodney W. Grimes 	needsep = 0;
4099b50d902SRodney W. Grimes }
4109b50d902SRodney W. Grimes 
4119b50d902SRodney W. Grimes void
4129b50d902SRodney W. Grimes outtwoline(F1, lp1, F2, lp2)
4139b50d902SRodney W. Grimes 	INPUT *F1, *F2;
4149b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
4159b50d902SRodney W. Grimes {
4169b50d902SRodney W. Grimes 	int cnt;
4179b50d902SRodney W. Grimes 
4189b50d902SRodney W. Grimes 	/* Output a pair of lines according to the join list (if any). */
4199b50d902SRodney W. Grimes 	if (olist)
4209b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt)
4219b50d902SRodney W. Grimes 			if (olist[cnt].filenum == 1)
4229b50d902SRodney W. Grimes 				outfield(lp1, olist[cnt].fieldno, 0);
4239b50d902SRodney W. Grimes 			else /* if (olist[cnt].filenum == 2) */
4249b50d902SRodney W. Grimes 				outfield(lp2, olist[cnt].fieldno, 0);
4259b50d902SRodney W. Grimes 	else {
4269b50d902SRodney W. Grimes 		/*
4279b50d902SRodney W. Grimes 		 * Output the join field, then the remaining fields from F1
4289b50d902SRodney W. Grimes 		 * and F2.
4299b50d902SRodney W. Grimes 		 */
4309b50d902SRodney W. Grimes 		outfield(lp1, F1->joinf, 0);
4319b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
4329b50d902SRodney W. Grimes 			if (F1->joinf != cnt)
4339b50d902SRodney W. Grimes 				outfield(lp1, cnt, 0);
4349b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
4359b50d902SRodney W. Grimes 			if (F2->joinf != cnt)
4369b50d902SRodney W. Grimes 				outfield(lp2, cnt, 0);
4379b50d902SRodney W. Grimes 	}
4389b50d902SRodney W. Grimes 	(void)printf("\n");
4399b50d902SRodney W. Grimes 	if (ferror(stdout))
4409b50d902SRodney W. Grimes 		err(1, "stdout");
4419b50d902SRodney W. Grimes 	needsep = 0;
4429b50d902SRodney W. Grimes }
4439b50d902SRodney W. Grimes 
4449b50d902SRodney W. Grimes void
4459b50d902SRodney W. Grimes outfield(lp, fieldno, out_empty)
4469b50d902SRodney W. Grimes 	LINE *lp;
4479b50d902SRodney W. Grimes 	u_long fieldno;
4489b50d902SRodney W. Grimes 	int out_empty;
4499b50d902SRodney W. Grimes {
4509b50d902SRodney W. Grimes 	if (needsep++)
4519b50d902SRodney W. Grimes 		(void)printf("%c", *tabchar);
4529b50d902SRodney W. Grimes 	if (!ferror(stdout))
4539b50d902SRodney W. Grimes 		if (lp->fieldcnt < fieldno || out_empty) {
4549b50d902SRodney W. Grimes 			if (empty != NULL)
4559b50d902SRodney W. Grimes 				(void)printf("%s", empty);
4569b50d902SRodney W. Grimes 		} else {
4579b50d902SRodney W. Grimes 			if (*lp->fields[fieldno] == '\0')
4589b50d902SRodney W. Grimes 				return;
4599b50d902SRodney W. Grimes 			(void)printf("%s", lp->fields[fieldno]);
4609b50d902SRodney W. Grimes 		}
4619b50d902SRodney W. Grimes 	if (ferror(stdout))
4629b50d902SRodney W. Grimes 		err(1, "stdout");
4639b50d902SRodney W. Grimes }
4649b50d902SRodney W. Grimes 
4659b50d902SRodney W. Grimes /*
4669b50d902SRodney W. Grimes  * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
4679b50d902SRodney W. Grimes  * fields.
4689b50d902SRodney W. Grimes  */
4699b50d902SRodney W. Grimes void
4709b50d902SRodney W. Grimes fieldarg(option)
4719b50d902SRodney W. Grimes 	char *option;
4729b50d902SRodney W. Grimes {
4739b50d902SRodney W. Grimes 	u_long fieldno;
4749b50d902SRodney W. Grimes 	char *end, *token;
4759b50d902SRodney W. Grimes 
4769b50d902SRodney W. Grimes 	while ((token = strsep(&option, " \t")) != NULL) {
4779b50d902SRodney W. Grimes 		if (*token == '\0')
4789b50d902SRodney W. Grimes 			continue;
4799b50d902SRodney W. Grimes 		if (token[0] != '1' && token[0] != '2' || token[1] != '.')
4809b50d902SRodney W. Grimes 			errx(1, "malformed -o option field");
4819b50d902SRodney W. Grimes 		fieldno = strtol(token + 2, &end, 10);
4829b50d902SRodney W. Grimes 		if (*end)
4839b50d902SRodney W. Grimes 			errx(1, "malformed -o option field");
4849b50d902SRodney W. Grimes 		if (fieldno == 0)
4859b50d902SRodney W. Grimes 			errx(1, "field numbers are 1 based");
4869b50d902SRodney W. Grimes 		if (olistcnt == olistalloc) {
4879b50d902SRodney W. Grimes 			olistalloc += 50;
4889b50d902SRodney W. Grimes 			if ((olist = realloc(olist,
4899b50d902SRodney W. Grimes 			    olistalloc * sizeof(OLIST))) == NULL)
4909b50d902SRodney W. Grimes 				err(1, NULL);
4919b50d902SRodney W. Grimes 		}
4929b50d902SRodney W. Grimes 		olist[olistcnt].filenum = token[0] - '0';
4939b50d902SRodney W. Grimes 		olist[olistcnt].fieldno = fieldno - 1;
4949b50d902SRodney W. Grimes 		++olistcnt;
4959b50d902SRodney W. Grimes 	}
4969b50d902SRodney W. Grimes }
4979b50d902SRodney W. Grimes 
4989b50d902SRodney W. Grimes void
4999b50d902SRodney W. Grimes obsolete(argv)
5009b50d902SRodney W. Grimes 	char **argv;
5019b50d902SRodney W. Grimes {
5029b50d902SRodney W. Grimes 	int len;
5039b50d902SRodney W. Grimes 	char **p, *ap, *t;
5049b50d902SRodney W. Grimes 
5059b50d902SRodney W. Grimes 	while ((ap = *++argv) != NULL) {
5069b50d902SRodney W. Grimes 		/* Return if "--". */
5079b50d902SRodney W. Grimes 		if (ap[0] == '-' && ap[1] == '-')
5089b50d902SRodney W. Grimes 			return;
5099b50d902SRodney W. Grimes 		switch (ap[1]) {
5109b50d902SRodney W. Grimes 		case 'a':
5119b50d902SRodney W. Grimes 			/*
5129b50d902SRodney W. Grimes 			 * The original join allowed "-a", which meant the
5139b50d902SRodney W. Grimes 			 * same as -a1 plus -a2.  POSIX 1003.2, Draft 11.2
5149b50d902SRodney W. Grimes 			 * only specifies this as "-a 1" and "a -2", so we
5159b50d902SRodney W. Grimes 			 * have to use another option flag, one that is
5169b50d902SRodney W. Grimes 			 * unlikely to ever be used or accidentally entered
5179b50d902SRodney W. Grimes 			 * on the command line.  (Well, we could reallocate
5189b50d902SRodney W. Grimes 			 * the argv array, but that hardly seems worthwhile.)
5199b50d902SRodney W. Grimes 			 */
5209b50d902SRodney W. Grimes 			if (ap[2] == '\0')
5219b50d902SRodney W. Grimes 				ap[1] = '\01';
5229b50d902SRodney W. Grimes 			break;
5239b50d902SRodney W. Grimes 		case 'j':
5249b50d902SRodney W. Grimes 			/*
5259b50d902SRodney W. Grimes 			 * The original join allowed "-j[12] arg" and "-j arg".
5269b50d902SRodney W. Grimes 			 * Convert the former to "-[12] arg".  Don't convert
5279b50d902SRodney W. Grimes 			 * the latter since getopt(3) can handle it.
5289b50d902SRodney W. Grimes 			 */
5299b50d902SRodney W. Grimes 			switch(ap[2]) {
5309b50d902SRodney W. Grimes 			case '1':
5319b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5329b50d902SRodney W. Grimes 					goto jbad;
5339b50d902SRodney W. Grimes 				ap[1] = '1';
5349b50d902SRodney W. Grimes 				ap[2] = '\0';
5359b50d902SRodney W. Grimes 				break;
5369b50d902SRodney W. Grimes 			case '2':
5379b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5389b50d902SRodney W. Grimes 					goto jbad;
5399b50d902SRodney W. Grimes 				ap[1] = '2';
5409b50d902SRodney W. Grimes 				ap[2] = '\0';
5419b50d902SRodney W. Grimes 				break;
5429b50d902SRodney W. Grimes 			case '\0':
5439b50d902SRodney W. Grimes 				break;
5449b50d902SRodney W. Grimes 			default:
5459b50d902SRodney W. Grimes jbad:				errx(1, "illegal option -- %s", ap);
5469b50d902SRodney W. Grimes 				usage();
5479b50d902SRodney W. Grimes 			}
5489b50d902SRodney W. Grimes 			break;
5499b50d902SRodney W. Grimes 		case 'o':
5509b50d902SRodney W. Grimes 			/*
5519b50d902SRodney W. Grimes 			 * The original join allowed "-o arg arg".
5529b50d902SRodney W. Grimes 			 * Convert to "-o arg -o arg".
5539b50d902SRodney W. Grimes 			 */
5549b50d902SRodney W. Grimes 			if (ap[2] != '\0')
5559b50d902SRodney W. Grimes 				break;
5569b50d902SRodney W. Grimes 			for (p = argv + 2; *p; ++p) {
5579b50d902SRodney W. Grimes 				if (p[0][0] != '1' &&
5589b50d902SRodney W. Grimes 				    p[0][0] != '2' || p[0][1] != '.')
5599b50d902SRodney W. Grimes 					break;
5609b50d902SRodney W. Grimes 				len = strlen(*p);
5619b50d902SRodney W. Grimes 				if (len - 2 != strspn(*p + 2, "0123456789"))
5629b50d902SRodney W. Grimes 					break;
5639b50d902SRodney W. Grimes 				if ((t = malloc(len + 3)) == NULL)
5649b50d902SRodney W. Grimes 					err(1, NULL);
5659b50d902SRodney W. Grimes 				t[0] = '-';
5669b50d902SRodney W. Grimes 				t[1] = 'o';
5679b50d902SRodney W. Grimes 				memmove(t + 2, *p, len + 1);
5689b50d902SRodney W. Grimes 				*p = t;
5699b50d902SRodney W. Grimes 			}
5709b50d902SRodney W. Grimes 			argv = p - 1;
5719b50d902SRodney W. Grimes 			break;
5729b50d902SRodney W. Grimes 		}
5739b50d902SRodney W. Grimes 	}
5749b50d902SRodney W. Grimes }
5759b50d902SRodney W. Grimes 
5769b50d902SRodney W. Grimes void
5779b50d902SRodney W. Grimes usage()
5789b50d902SRodney W. Grimes {
5799b50d902SRodney W. Grimes 	(void)fprintf(stderr, "%s%s\n",
5809b50d902SRodney W. Grimes 	    "usage: join [-a fileno | -v fileno ] [-e string] [-1 field] ",
5819b50d902SRodney W. Grimes 	    "[-2 field]\n            [-o list] [-t char] file1 file2");
5829b50d902SRodney W. Grimes 	exit(1);
5839b50d902SRodney W. Grimes }
584