xref: /freebsd/usr.bin/join/join.c (revision 60d1fdaa4703740d320d9182fba563cfe7c51901)
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[] =
49c3aac50fSPeter Wemm   "$FreeBSD$";
509b50d902SRodney W. Grimes #endif /* not lint */
519b50d902SRodney W. Grimes 
529b50d902SRodney W. Grimes #include <sys/param.h>
539b50d902SRodney W. Grimes 
549b50d902SRodney W. Grimes #include <err.h>
559b50d902SRodney W. Grimes #include <errno.h>
56fcb11314STim J. Robbins #include <locale.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;
8860d1fdaaSMark Murray INPUT input1 = { NULL, 0, 0, 1, NULL, 0, 0, 0, 0 },
8960d1fdaaSMark Murray       input2 = { NULL, 0, 0, 2, NULL, 0, 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) */
10360d1fdaaSMark Murray static char default_tabchar[] = " \t";
10460d1fdaaSMark Murray char *tabchar = default_tabchar;/* delimiter characters (-t) */
1059b50d902SRodney W. Grimes 
106f1bb2cd2SWarner Losh int  cmp(LINE *, u_long, LINE *, u_long);
107f1bb2cd2SWarner Losh void fieldarg(char *);
108f1bb2cd2SWarner Losh void joinlines(INPUT *, INPUT *);
109f1bb2cd2SWarner Losh void obsolete(char **);
110f1bb2cd2SWarner Losh void outfield(LINE *, u_long, int);
111f1bb2cd2SWarner Losh void outoneline(INPUT *, LINE *);
112f1bb2cd2SWarner Losh void outtwoline(INPUT *, LINE *, INPUT *, LINE *);
113f1bb2cd2SWarner Losh void slurp(INPUT *);
114f1bb2cd2SWarner Losh void usage(void);
1159b50d902SRodney W. Grimes 
1169b50d902SRodney W. Grimes int
1179b50d902SRodney W. Grimes main(argc, argv)
1189b50d902SRodney W. Grimes 	int argc;
1199b50d902SRodney W. Grimes 	char *argv[];
1209b50d902SRodney W. Grimes {
1219b50d902SRodney W. Grimes 	INPUT *F1, *F2;
1229b50d902SRodney W. Grimes 	int aflag, ch, cval, vflag;
1239b50d902SRodney W. Grimes 	char *end;
1249b50d902SRodney W. Grimes 
125fcb11314STim J. Robbins 	setlocale(LC_ALL, "");
126fcb11314STim J. Robbins 
1279b50d902SRodney W. Grimes 	F1 = &input1;
1289b50d902SRodney W. Grimes 	F2 = &input2;
1299b50d902SRodney W. Grimes 
1309b50d902SRodney W. Grimes 	aflag = vflag = 0;
1319b50d902SRodney W. Grimes 	obsolete(argv);
1321c8af878SWarner Losh 	while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) {
1339b50d902SRodney W. Grimes 		switch (ch) {
1349b50d902SRodney W. Grimes 		case '\01':		/* See comment in obsolete(). */
1359b50d902SRodney W. Grimes 			aflag = 1;
1369b50d902SRodney W. Grimes 			F1->unpair = F2->unpair = 1;
1379b50d902SRodney W. Grimes 			break;
1389b50d902SRodney W. Grimes 		case '1':
1399b50d902SRodney W. Grimes 			if ((F1->joinf = strtol(optarg, &end, 10)) < 1)
1409b50d902SRodney W. Grimes 				errx(1, "-1 option field number less than 1");
1419b50d902SRodney W. Grimes 			if (*end)
1429b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1439b50d902SRodney W. Grimes 			--F1->joinf;
1449b50d902SRodney W. Grimes 			break;
1459b50d902SRodney W. Grimes 		case '2':
1469b50d902SRodney W. Grimes 			if ((F2->joinf = strtol(optarg, &end, 10)) < 1)
1479b50d902SRodney W. Grimes 				errx(1, "-2 option field number less than 1");
1489b50d902SRodney W. Grimes 			if (*end)
1499b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1509b50d902SRodney W. Grimes 			--F2->joinf;
1519b50d902SRodney W. Grimes 			break;
1529b50d902SRodney W. Grimes 		case 'a':
1539b50d902SRodney W. Grimes 			aflag = 1;
1549b50d902SRodney W. Grimes 			switch(strtol(optarg, &end, 10)) {
1559b50d902SRodney W. Grimes 			case 1:
1569b50d902SRodney W. Grimes 				F1->unpair = 1;
1579b50d902SRodney W. Grimes 				break;
1589b50d902SRodney W. Grimes 			case 2:
1599b50d902SRodney W. Grimes 				F2->unpair = 1;
1609b50d902SRodney W. Grimes 				break;
1619b50d902SRodney W. Grimes 			default:
1629b50d902SRodney W. Grimes 				errx(1, "-a option file number not 1 or 2");
1639b50d902SRodney W. Grimes 				break;
1649b50d902SRodney W. Grimes 			}
1659b50d902SRodney W. Grimes 			if (*end)
1669b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
1679b50d902SRodney W. Grimes 			break;
1689b50d902SRodney W. Grimes 		case 'e':
1699b50d902SRodney W. Grimes 			empty = optarg;
1709b50d902SRodney W. Grimes 			break;
1719b50d902SRodney W. Grimes 		case 'j':
1729b50d902SRodney W. Grimes 			if ((F1->joinf = F2->joinf =
1739b50d902SRodney W. Grimes 			    strtol(optarg, &end, 10)) < 1)
1749b50d902SRodney W. Grimes 				errx(1, "-j option field number less than 1");
1759b50d902SRodney W. Grimes 			if (*end)
1769b50d902SRodney W. Grimes 				errx(1, "illegal field number -- %s", optarg);
1779b50d902SRodney W. Grimes 			--F1->joinf;
1789b50d902SRodney W. Grimes 			--F2->joinf;
1799b50d902SRodney W. Grimes 			break;
1809b50d902SRodney W. Grimes 		case 'o':
1819b50d902SRodney W. Grimes 			fieldarg(optarg);
1829b50d902SRodney W. Grimes 			break;
1839b50d902SRodney W. Grimes 		case 't':
1849b50d902SRodney W. Grimes 			spans = 0;
1859b50d902SRodney W. Grimes 			if (strlen(tabchar = optarg) != 1)
1869b50d902SRodney W. Grimes 				errx(1, "illegal tab character specification");
1879b50d902SRodney W. Grimes 			break;
1889b50d902SRodney W. Grimes 		case 'v':
1899b50d902SRodney W. Grimes 			vflag = 1;
1909b50d902SRodney W. Grimes 			joinout = 0;
1919b50d902SRodney W. Grimes 			switch (strtol(optarg, &end, 10)) {
1929b50d902SRodney W. Grimes 			case 1:
1939b50d902SRodney W. Grimes 				F1->unpair = 1;
1949b50d902SRodney W. Grimes 				break;
1959b50d902SRodney W. Grimes 			case 2:
1969b50d902SRodney W. Grimes 				F2->unpair = 1;
1979b50d902SRodney W. Grimes 				break;
1989b50d902SRodney W. Grimes 			default:
1999b50d902SRodney W. Grimes 				errx(1, "-v option file number not 1 or 2");
2009b50d902SRodney W. Grimes 				break;
2019b50d902SRodney W. Grimes 			}
2029b50d902SRodney W. Grimes 			if (*end)
2039b50d902SRodney W. Grimes 				errx(1, "illegal file number -- %s", optarg);
2049b50d902SRodney W. Grimes 			break;
2059b50d902SRodney W. Grimes 		case '?':
2069b50d902SRodney W. Grimes 		default:
2079b50d902SRodney W. Grimes 			usage();
2089b50d902SRodney W. Grimes 		}
2099b50d902SRodney W. Grimes 	}
2109b50d902SRodney W. Grimes 	argc -= optind;
2119b50d902SRodney W. Grimes 	argv += optind;
2129b50d902SRodney W. Grimes 
2139b50d902SRodney W. Grimes 	if (aflag && vflag)
2149b50d902SRodney W. Grimes 		errx(1, "the -a and -v options are mutually exclusive");
2159b50d902SRodney W. Grimes 
2169b50d902SRodney W. Grimes 	if (argc != 2)
2179b50d902SRodney W. Grimes 		usage();
2189b50d902SRodney W. Grimes 
2199b50d902SRodney W. Grimes 	/* Open the files; "-" means stdin. */
2209b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2219b50d902SRodney W. Grimes 		F1->fp = stdin;
2229b50d902SRodney W. Grimes 	else if ((F1->fp = fopen(*argv, "r")) == NULL)
2239b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2249b50d902SRodney W. Grimes 	++argv;
2259b50d902SRodney W. Grimes 	if (!strcmp(*argv, "-"))
2269b50d902SRodney W. Grimes 		F2->fp = stdin;
2279b50d902SRodney W. Grimes 	else if ((F2->fp = fopen(*argv, "r")) == NULL)
2289b50d902SRodney W. Grimes 		err(1, "%s", *argv);
2299b50d902SRodney W. Grimes 	if (F1->fp == stdin && F2->fp == stdin)
2309b50d902SRodney W. Grimes 		errx(1, "only one input file may be stdin");
2319b50d902SRodney W. Grimes 
2329b50d902SRodney W. Grimes 	slurp(F1);
2339b50d902SRodney W. Grimes 	slurp(F2);
2349b50d902SRodney W. Grimes 	while (F1->setcnt && F2->setcnt) {
2359b50d902SRodney W. Grimes 		cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf);
2369b50d902SRodney W. Grimes 		if (cval == 0) {
2379b50d902SRodney W. Grimes 			/* Oh joy, oh rapture, oh beauty divine! */
2389b50d902SRodney W. Grimes 			if (joinout)
2399b50d902SRodney W. Grimes 				joinlines(F1, F2);
2409b50d902SRodney W. Grimes 			slurp(F1);
2419b50d902SRodney W. Grimes 			slurp(F2);
2429b50d902SRodney W. Grimes 		} else if (cval < 0) {
2439b50d902SRodney W. Grimes 			/* File 1 takes the lead... */
2449b50d902SRodney W. Grimes 			if (F1->unpair)
2459b50d902SRodney W. Grimes 				joinlines(F1, NULL);
2469b50d902SRodney W. Grimes 			slurp(F1);
2479b50d902SRodney W. Grimes 		} else {
2489b50d902SRodney W. Grimes 			/* File 2 takes the lead... */
2499b50d902SRodney W. Grimes 			if (F2->unpair)
2509b50d902SRodney W. Grimes 				joinlines(F2, NULL);
2519b50d902SRodney W. Grimes 			slurp(F2);
2529b50d902SRodney W. Grimes 		}
2539b50d902SRodney W. Grimes 	}
2549b50d902SRodney W. Grimes 
2559b50d902SRodney W. Grimes 	/*
2569b50d902SRodney W. Grimes 	 * Now that one of the files is used up, optionally output any
2579b50d902SRodney W. Grimes 	 * remaining lines from the other file.
2589b50d902SRodney W. Grimes 	 */
2599b50d902SRodney W. Grimes 	if (F1->unpair)
2609b50d902SRodney W. Grimes 		while (F1->setcnt) {
2619b50d902SRodney W. Grimes 			joinlines(F1, NULL);
2629b50d902SRodney W. Grimes 			slurp(F1);
2639b50d902SRodney W. Grimes 		}
2649b50d902SRodney W. Grimes 	if (F2->unpair)
2659b50d902SRodney W. Grimes 		while (F2->setcnt) {
2669b50d902SRodney W. Grimes 			joinlines(F2, NULL);
2679b50d902SRodney W. Grimes 			slurp(F2);
2689b50d902SRodney W. Grimes 		}
2699b50d902SRodney W. Grimes 	exit(0);
2709b50d902SRodney W. Grimes }
2719b50d902SRodney W. Grimes 
2729b50d902SRodney W. Grimes void
2739b50d902SRodney W. Grimes slurp(F)
2749b50d902SRodney W. Grimes 	INPUT *F;
2759b50d902SRodney W. Grimes {
2769b50d902SRodney W. Grimes 	LINE *lp, *lastlp, tmp;
2779b50d902SRodney W. Grimes 	size_t len;
2789b50d902SRodney W. Grimes 	int cnt;
2799b50d902SRodney W. Grimes 	char *bp, *fieldp;
2809b50d902SRodney W. Grimes 
2819b50d902SRodney W. Grimes 	/*
2829b50d902SRodney W. Grimes 	 * Read all of the lines from an input file that have the same
2839b50d902SRodney W. Grimes 	 * join field.
2849b50d902SRodney W. Grimes 	 */
2859b50d902SRodney W. Grimes 	F->setcnt = 0;
286bf6570c4SPoul-Henning Kamp 	for (lastlp = NULL;; ++F->setcnt) {
2879b50d902SRodney W. Grimes 		/*
2889b50d902SRodney W. Grimes 		 * If we're out of space to hold line structures, allocate
2899b50d902SRodney W. Grimes 		 * more.  Initialize the structure so that we know that this
2909b50d902SRodney W. Grimes 		 * is new space.
2919b50d902SRodney W. Grimes 		 */
2929b50d902SRodney W. Grimes 		if (F->setcnt == F->setalloc) {
2939b50d902SRodney W. Grimes 			cnt = F->setalloc;
2949b50d902SRodney W. Grimes 			F->setalloc += 50;
2959b50d902SRodney W. Grimes 			if ((F->set = realloc(F->set,
2969b50d902SRodney W. Grimes 			    F->setalloc * sizeof(LINE))) == NULL)
2979b50d902SRodney W. Grimes 				err(1, NULL);
2989b50d902SRodney W. Grimes 			memset(F->set + cnt, 0, 50 * sizeof(LINE));
2996f0a860fSPeter Wemm 
3006f0a860fSPeter Wemm 			/* re-set lastlp in case it moved */
3016f0a860fSPeter Wemm 			if (lastlp != NULL)
3026f0a860fSPeter Wemm 				lastlp = &F->set[F->setcnt - 1];
3039b50d902SRodney W. Grimes 		}
3049b50d902SRodney W. Grimes 
3059b50d902SRodney W. Grimes 		/*
3069b50d902SRodney W. Grimes 		 * Get any pushed back line, else get the next line.  Allocate
3079b50d902SRodney W. Grimes 		 * space as necessary.  If taking the line from the stack swap
3089b50d902SRodney W. Grimes 		 * the two structures so that we don't lose space allocated to
3099b50d902SRodney W. Grimes 		 * either structure.  This could be avoided by doing another
3109b50d902SRodney W. Grimes 		 * level of indirection, but it's probably okay as is.
3119b50d902SRodney W. Grimes 		 */
3129b50d902SRodney W. Grimes 		lp = &F->set[F->setcnt];
313bf6570c4SPoul-Henning Kamp 		if (F->setcnt)
314bf6570c4SPoul-Henning Kamp 			lastlp = &F->set[F->setcnt - 1];
3159b50d902SRodney W. Grimes 		if (F->pushbool) {
3169b50d902SRodney W. Grimes 			tmp = F->set[F->setcnt];
3179b50d902SRodney W. Grimes 			F->set[F->setcnt] = F->set[F->pushback];
3189b50d902SRodney W. Grimes 			F->set[F->pushback] = tmp;
3199b50d902SRodney W. Grimes 			F->pushbool = 0;
3209b50d902SRodney W. Grimes 			continue;
3219b50d902SRodney W. Grimes 		}
3229b50d902SRodney W. Grimes 		if ((bp = fgetln(F->fp, &len)) == NULL)
3239b50d902SRodney W. Grimes 			return;
3249b50d902SRodney W. Grimes 		if (lp->linealloc <= len + 1) {
3256f0a860fSPeter Wemm 			lp->linealloc += MAX(100, len + 1 - lp->linealloc);
3269b50d902SRodney W. Grimes 			if ((lp->line =
3279b50d902SRodney W. Grimes 			    realloc(lp->line, lp->linealloc)) == NULL)
3289b50d902SRodney W. Grimes 				err(1, NULL);
3299b50d902SRodney W. Grimes 		}
3309b50d902SRodney W. Grimes 		memmove(lp->line, bp, len);
3319b50d902SRodney W. Grimes 
3329b50d902SRodney W. Grimes 		/* Replace trailing newline, if it exists. */
3339b50d902SRodney W. Grimes 		if (bp[len - 1] == '\n')
3349b50d902SRodney W. Grimes 			lp->line[len - 1] = '\0';
3359b50d902SRodney W. Grimes 		else
3369b50d902SRodney W. Grimes 			lp->line[len] = '\0';
3379b50d902SRodney W. Grimes 		bp = lp->line;
3389b50d902SRodney W. Grimes 
3399b50d902SRodney W. Grimes 		/* Split the line into fields, allocate space as necessary. */
3409b50d902SRodney W. Grimes 		lp->fieldcnt = 0;
3419b50d902SRodney W. Grimes 		while ((fieldp = strsep(&bp, tabchar)) != NULL) {
3429b50d902SRodney W. Grimes 			if (spans && *fieldp == '\0')
3439b50d902SRodney W. Grimes 				continue;
3449b50d902SRodney W. Grimes 			if (lp->fieldcnt == lp->fieldalloc) {
3459b50d902SRodney W. Grimes 				lp->fieldalloc += 50;
3469b50d902SRodney W. Grimes 				if ((lp->fields = realloc(lp->fields,
3479b50d902SRodney W. Grimes 				    lp->fieldalloc * sizeof(char *))) == NULL)
3489b50d902SRodney W. Grimes 					err(1, NULL);
3499b50d902SRodney W. Grimes 			}
3509b50d902SRodney W. Grimes 			lp->fields[lp->fieldcnt++] = fieldp;
3519b50d902SRodney W. Grimes 		}
3529b50d902SRodney W. Grimes 
3539b50d902SRodney W. Grimes 		/* See if the join field value has changed. */
3549b50d902SRodney W. Grimes 		if (lastlp != NULL && cmp(lp, F->joinf, lastlp, F->joinf)) {
3559b50d902SRodney W. Grimes 			F->pushbool = 1;
3569b50d902SRodney W. Grimes 			F->pushback = F->setcnt;
3579b50d902SRodney W. Grimes 			break;
3589b50d902SRodney W. Grimes 		}
3599b50d902SRodney W. Grimes 	}
3609b50d902SRodney W. Grimes }
3619b50d902SRodney W. Grimes 
3629b50d902SRodney W. Grimes int
3639b50d902SRodney W. Grimes cmp(lp1, fieldno1, lp2, fieldno2)
3649b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
3659b50d902SRodney W. Grimes 	u_long fieldno1, fieldno2;
3669b50d902SRodney W. Grimes {
367cefe4f6fSJoerg Wunsch 	if (lp1->fieldcnt <= fieldno1)
3686f0a860fSPeter Wemm 		return (lp2->fieldcnt <= fieldno2 ? 0 : 1);
369cefe4f6fSJoerg Wunsch 	if (lp2->fieldcnt <= fieldno2)
3709b50d902SRodney W. Grimes 		return (-1);
371fcb11314STim J. Robbins 	return (strcoll(lp1->fields[fieldno1], lp2->fields[fieldno2]));
3729b50d902SRodney W. Grimes }
3739b50d902SRodney W. Grimes 
3749b50d902SRodney W. Grimes void
3759b50d902SRodney W. Grimes joinlines(F1, F2)
3769b50d902SRodney W. Grimes 	INPUT *F1, *F2;
3779b50d902SRodney W. Grimes {
37860d1fdaaSMark Murray 	unsigned int cnt1, cnt2;
3799b50d902SRodney W. Grimes 
3809b50d902SRodney W. Grimes 	/*
3819b50d902SRodney W. Grimes 	 * Output the results of a join comparison.  The output may be from
3829b50d902SRodney W. Grimes 	 * either file 1 or file 2 (in which case the first argument is the
3839b50d902SRodney W. Grimes 	 * file from which to output) or from both.
3849b50d902SRodney W. Grimes 	 */
3859b50d902SRodney W. Grimes 	if (F2 == NULL) {
3869b50d902SRodney W. Grimes 		for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3879b50d902SRodney W. Grimes 			outoneline(F1, &F1->set[cnt1]);
3889b50d902SRodney W. Grimes 		return;
3899b50d902SRodney W. Grimes 	}
3909b50d902SRodney W. Grimes 	for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1)
3919b50d902SRodney W. Grimes 		for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2)
3929b50d902SRodney W. Grimes 			outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]);
3939b50d902SRodney W. Grimes }
3949b50d902SRodney W. Grimes 
3959b50d902SRodney W. Grimes void
3969b50d902SRodney W. Grimes outoneline(F, lp)
3979b50d902SRodney W. Grimes 	INPUT *F;
3989b50d902SRodney W. Grimes 	LINE *lp;
3999b50d902SRodney W. Grimes {
40060d1fdaaSMark Murray 	unsigned int cnt;
4019b50d902SRodney W. Grimes 
4029b50d902SRodney W. Grimes 	/*
4039b50d902SRodney W. Grimes 	 * Output a single line from one of the files, according to the
4049b50d902SRodney W. Grimes 	 * join rules.  This happens when we are writing unmatched single
4059b50d902SRodney W. Grimes 	 * lines.  Output empty fields in the right places.
4069b50d902SRodney W. Grimes 	 */
4079b50d902SRodney W. Grimes 	if (olist)
4089b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt) {
40960d1fdaaSMark Murray 			if (olist[cnt].filenum == (unsigned)F->number)
4109b50d902SRodney W. Grimes 				outfield(lp, olist[cnt].fieldno, 0);
411dcd587c9SJuli Mallett 			else if (olist[cnt].filenum == 0)
412dcd587c9SJuli Mallett 				outfield(lp, F->joinf, 0);
4139b50d902SRodney W. Grimes 			else
4149b50d902SRodney W. Grimes 				outfield(lp, 0, 1);
4159b50d902SRodney W. Grimes 		}
4169b50d902SRodney W. Grimes 	else
4179b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp->fieldcnt; ++cnt)
4189b50d902SRodney W. Grimes 			outfield(lp, cnt, 0);
4199b50d902SRodney W. Grimes 	(void)printf("\n");
4209b50d902SRodney W. Grimes 	if (ferror(stdout))
4219b50d902SRodney W. Grimes 		err(1, "stdout");
4229b50d902SRodney W. Grimes 	needsep = 0;
4239b50d902SRodney W. Grimes }
4249b50d902SRodney W. Grimes 
4259b50d902SRodney W. Grimes void
4269b50d902SRodney W. Grimes outtwoline(F1, lp1, F2, lp2)
4279b50d902SRodney W. Grimes 	INPUT *F1, *F2;
4289b50d902SRodney W. Grimes 	LINE *lp1, *lp2;
4299b50d902SRodney W. Grimes {
43060d1fdaaSMark Murray 	unsigned int cnt;
4319b50d902SRodney W. Grimes 
4329b50d902SRodney W. Grimes 	/* Output a pair of lines according to the join list (if any). */
4339b50d902SRodney W. Grimes 	if (olist)
4349b50d902SRodney W. Grimes 		for (cnt = 0; cnt < olistcnt; ++cnt)
435dcd587c9SJuli Mallett 			if (olist[cnt].filenum == 0) {
436dcd587c9SJuli Mallett 				if (lp1->fieldcnt >= F1->joinf)
437dcd587c9SJuli Mallett 					outfield(lp1, F1->joinf, 0);
438dcd587c9SJuli Mallett 				else
439dcd587c9SJuli Mallett 					outfield(lp2, F2->joinf, 0);
440dcd587c9SJuli Mallett 			} else if (olist[cnt].filenum == 1)
4419b50d902SRodney W. Grimes 				outfield(lp1, olist[cnt].fieldno, 0);
4429b50d902SRodney W. Grimes 			else /* if (olist[cnt].filenum == 2) */
4439b50d902SRodney W. Grimes 				outfield(lp2, olist[cnt].fieldno, 0);
4449b50d902SRodney W. Grimes 	else {
4459b50d902SRodney W. Grimes 		/*
4469b50d902SRodney W. Grimes 		 * Output the join field, then the remaining fields from F1
4479b50d902SRodney W. Grimes 		 * and F2.
4489b50d902SRodney W. Grimes 		 */
4499b50d902SRodney W. Grimes 		outfield(lp1, F1->joinf, 0);
4509b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp1->fieldcnt; ++cnt)
4519b50d902SRodney W. Grimes 			if (F1->joinf != cnt)
4529b50d902SRodney W. Grimes 				outfield(lp1, cnt, 0);
4539b50d902SRodney W. Grimes 		for (cnt = 0; cnt < lp2->fieldcnt; ++cnt)
4549b50d902SRodney W. Grimes 			if (F2->joinf != cnt)
4559b50d902SRodney W. Grimes 				outfield(lp2, cnt, 0);
4569b50d902SRodney W. Grimes 	}
4579b50d902SRodney W. Grimes 	(void)printf("\n");
4589b50d902SRodney W. Grimes 	if (ferror(stdout))
4599b50d902SRodney W. Grimes 		err(1, "stdout");
4609b50d902SRodney W. Grimes 	needsep = 0;
4619b50d902SRodney W. Grimes }
4629b50d902SRodney W. Grimes 
4639b50d902SRodney W. Grimes void
4649b50d902SRodney W. Grimes outfield(lp, fieldno, out_empty)
4659b50d902SRodney W. Grimes 	LINE *lp;
4669b50d902SRodney W. Grimes 	u_long fieldno;
4679b50d902SRodney W. Grimes 	int out_empty;
4689b50d902SRodney W. Grimes {
4699b50d902SRodney W. Grimes 	if (needsep++)
4709b50d902SRodney W. Grimes 		(void)printf("%c", *tabchar);
4719ef5c48bSBill Fumerola 	if (!ferror(stdout)) {
472cefe4f6fSJoerg Wunsch 		if (lp->fieldcnt <= fieldno || out_empty) {
4739b50d902SRodney W. Grimes 			if (empty != NULL)
4749b50d902SRodney W. Grimes 				(void)printf("%s", empty);
4759b50d902SRodney W. Grimes 		} else {
4769b50d902SRodney W. Grimes 			if (*lp->fields[fieldno] == '\0')
4779b50d902SRodney W. Grimes 				return;
4789b50d902SRodney W. Grimes 			(void)printf("%s", lp->fields[fieldno]);
4799b50d902SRodney W. Grimes 		}
4809ef5c48bSBill Fumerola 	}
4819b50d902SRodney W. Grimes 	if (ferror(stdout))
4829b50d902SRodney W. Grimes 		err(1, "stdout");
4839b50d902SRodney W. Grimes }
4849b50d902SRodney W. Grimes 
4859b50d902SRodney W. Grimes /*
4869b50d902SRodney W. Grimes  * Convert an output list argument "2.1, 1.3, 2.4" into an array of output
4879b50d902SRodney W. Grimes  * fields.
4889b50d902SRodney W. Grimes  */
4899b50d902SRodney W. Grimes void
4909b50d902SRodney W. Grimes fieldarg(option)
4919b50d902SRodney W. Grimes 	char *option;
4929b50d902SRodney W. Grimes {
493dcd587c9SJuli Mallett 	u_long fieldno, filenum;
4949b50d902SRodney W. Grimes 	char *end, *token;
4959b50d902SRodney W. Grimes 
4966f0a860fSPeter Wemm 	while ((token = strsep(&option, ", \t")) != NULL) {
4979b50d902SRodney W. Grimes 		if (*token == '\0')
4989b50d902SRodney W. Grimes 			continue;
499dcd587c9SJuli Mallett 		if (token[0] == '0')
500dcd587c9SJuli Mallett 			filenum = fieldno = 0;
501dcd587c9SJuli Mallett 		else if ((token[0] == '1' || token[0] == '2') &&
502dcd587c9SJuli Mallett 		    token[1] == '.') {
503dcd587c9SJuli Mallett 			filenum = token[0] - '0';
5049b50d902SRodney W. Grimes 			fieldno = strtol(token + 2, &end, 10);
5059b50d902SRodney W. Grimes 			if (*end)
5069b50d902SRodney W. Grimes 				errx(1, "malformed -o option field");
5079b50d902SRodney W. Grimes 			if (fieldno == 0)
5089b50d902SRodney W. Grimes 				errx(1, "field numbers are 1 based");
509dcd587c9SJuli Mallett 			--fieldno;
510dcd587c9SJuli Mallett 		} else
511dcd587c9SJuli Mallett 			errx(1, "malformed -o option field");
5129b50d902SRodney W. Grimes 		if (olistcnt == olistalloc) {
5139b50d902SRodney W. Grimes 			olistalloc += 50;
5149b50d902SRodney W. Grimes 			if ((olist = realloc(olist,
5159b50d902SRodney W. Grimes 			    olistalloc * sizeof(OLIST))) == NULL)
5169b50d902SRodney W. Grimes 				err(1, NULL);
5179b50d902SRodney W. Grimes 		}
518dcd587c9SJuli Mallett 		olist[olistcnt].filenum = filenum;
519dcd587c9SJuli Mallett 		olist[olistcnt].fieldno = fieldno;
5209b50d902SRodney W. Grimes 		++olistcnt;
5219b50d902SRodney W. Grimes 	}
5229b50d902SRodney W. Grimes }
5239b50d902SRodney W. Grimes 
5249b50d902SRodney W. Grimes void
5259b50d902SRodney W. Grimes obsolete(argv)
5269b50d902SRodney W. Grimes 	char **argv;
5279b50d902SRodney W. Grimes {
52860d1fdaaSMark Murray 	unsigned int len;
5299b50d902SRodney W. Grimes 	char **p, *ap, *t;
5309b50d902SRodney W. Grimes 
5319b50d902SRodney W. Grimes 	while ((ap = *++argv) != NULL) {
5329b50d902SRodney W. Grimes 		/* Return if "--". */
5339b50d902SRodney W. Grimes 		if (ap[0] == '-' && ap[1] == '-')
5349b50d902SRodney W. Grimes 			return;
535faba086bSJonathan Lemon 		/* skip if not an option */
536faba086bSJonathan Lemon 		if (ap[0] != '-')
537faba086bSJonathan Lemon 			continue;
5389b50d902SRodney W. Grimes 		switch (ap[1]) {
5399b50d902SRodney W. Grimes 		case 'a':
5409b50d902SRodney W. Grimes 			/*
5419b50d902SRodney W. Grimes 			 * The original join allowed "-a", which meant the
5429b50d902SRodney W. Grimes 			 * same as -a1 plus -a2.  POSIX 1003.2, Draft 11.2
5439b50d902SRodney W. Grimes 			 * only specifies this as "-a 1" and "a -2", so we
5449b50d902SRodney W. Grimes 			 * have to use another option flag, one that is
5459b50d902SRodney W. Grimes 			 * unlikely to ever be used or accidentally entered
5469b50d902SRodney W. Grimes 			 * on the command line.  (Well, we could reallocate
5479b50d902SRodney W. Grimes 			 * the argv array, but that hardly seems worthwhile.)
5489b50d902SRodney W. Grimes 			 */
549fcb11314STim J. Robbins 			if (ap[2] == '\0' && (argv[1] == NULL ||
550fcb11314STim J. Robbins 			    (strcmp(argv[1], "1") != 0 &&
551fcb11314STim J. Robbins 			    strcmp(argv[1], "2") != 0))) {
5529b50d902SRodney W. Grimes 				ap[1] = '\01';
553fcb11314STim J. Robbins 				warnx("-a option used without an argument; "
554fcb11314STim J. Robbins 				    "reverting to historical behavior");
555fcb11314STim J. Robbins 			}
5569b50d902SRodney W. Grimes 			break;
5579b50d902SRodney W. Grimes 		case 'j':
5589b50d902SRodney W. Grimes 			/*
5599b50d902SRodney W. Grimes 			 * The original join allowed "-j[12] arg" and "-j arg".
5609b50d902SRodney W. Grimes 			 * Convert the former to "-[12] arg".  Don't convert
5619b50d902SRodney W. Grimes 			 * the latter since getopt(3) can handle it.
5629b50d902SRodney W. Grimes 			 */
5639b50d902SRodney W. Grimes 			switch(ap[2]) {
5649b50d902SRodney W. Grimes 			case '1':
5659b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5669b50d902SRodney W. Grimes 					goto jbad;
5679b50d902SRodney W. Grimes 				ap[1] = '1';
5689b50d902SRodney W. Grimes 				ap[2] = '\0';
5699b50d902SRodney W. Grimes 				break;
5709b50d902SRodney W. Grimes 			case '2':
5719b50d902SRodney W. Grimes 				if (ap[3] != '\0')
5729b50d902SRodney W. Grimes 					goto jbad;
5739b50d902SRodney W. Grimes 				ap[1] = '2';
5749b50d902SRodney W. Grimes 				ap[2] = '\0';
5759b50d902SRodney W. Grimes 				break;
5769b50d902SRodney W. Grimes 			case '\0':
5779b50d902SRodney W. Grimes 				break;
5789b50d902SRodney W. Grimes 			default:
5799b50d902SRodney W. Grimes jbad:				errx(1, "illegal option -- %s", ap);
5809b50d902SRodney W. Grimes 				usage();
5819b50d902SRodney W. Grimes 			}
5829b50d902SRodney W. Grimes 			break;
5839b50d902SRodney W. Grimes 		case 'o':
5849b50d902SRodney W. Grimes 			/*
5859b50d902SRodney W. Grimes 			 * The original join allowed "-o arg arg".
5869b50d902SRodney W. Grimes 			 * Convert to "-o arg -o arg".
5879b50d902SRodney W. Grimes 			 */
5889b50d902SRodney W. Grimes 			if (ap[2] != '\0')
5899b50d902SRodney W. Grimes 				break;
5909b50d902SRodney W. Grimes 			for (p = argv + 2; *p; ++p) {
591dcd587c9SJuli Mallett 				if (p[0][0] == '0' || (p[0][0] != '1' &&
592dcd587c9SJuli Mallett 				    p[0][0] != '2' || p[0][1] != '.'))
5939b50d902SRodney W. Grimes 					break;
5949b50d902SRodney W. Grimes 				len = strlen(*p);
5959b50d902SRodney W. Grimes 				if (len - 2 != strspn(*p + 2, "0123456789"))
5969b50d902SRodney W. Grimes 					break;
5979b50d902SRodney W. Grimes 				if ((t = malloc(len + 3)) == NULL)
5989b50d902SRodney W. Grimes 					err(1, NULL);
5999b50d902SRodney W. Grimes 				t[0] = '-';
6009b50d902SRodney W. Grimes 				t[1] = 'o';
6019b50d902SRodney W. Grimes 				memmove(t + 2, *p, len + 1);
6029b50d902SRodney W. Grimes 				*p = t;
6039b50d902SRodney W. Grimes 			}
6049b50d902SRodney W. Grimes 			argv = p - 1;
6059b50d902SRodney W. Grimes 			break;
6069b50d902SRodney W. Grimes 		}
6079b50d902SRodney W. Grimes 	}
6089b50d902SRodney W. Grimes }
6099b50d902SRodney W. Grimes 
6109b50d902SRodney W. Grimes void
6119b50d902SRodney W. Grimes usage()
6129b50d902SRodney W. Grimes {
61321a3d165SPhilippe Charnier 	(void)fprintf(stderr, "%s %s\n%s\n",
6149b50d902SRodney W. Grimes 	    "usage: join [-a fileno | -v fileno ] [-e string] [-1 field]",
61521a3d165SPhilippe Charnier 	    "[-2 field]",
61621a3d165SPhilippe Charnier 		"            [-o list] [-t char] file1 file2");
6179b50d902SRodney W. Grimes 	exit(1);
6189b50d902SRodney W. Grimes }
619