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