source: trunk/coreutils/src/join.c@ 2560

Last change on this file since 2560 was 2560, checked in by bird, 20 years ago

Initial porting.

File size: 22.4 KB
Line 
1/* join - join lines of two files on a common field
2 Copyright (C) 91, 1995-2005 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 Written by Mike Haertel, [email protected]. */
19
20#include <config.h>
21
22#include <assert.h>
23#include <sys/types.h>
24#include <getopt.h>
25
26#include "system.h"
27#include "error.h"
28#include "hard-locale.h"
29#include "linebuffer.h"
30#include "memcasecmp.h"
31#include "quote.h"
32#include "stdio--.h"
33#include "xmemcoll.h"
34#include "xstrtol.h"
35
36/* The official name of this program (e.g., no `g' prefix). */
37#define PROGRAM_NAME "join"
38
39#define AUTHORS "Mike Haertel"
40
41#define join system_join
42
43/* An element of the list identifying which fields to print for each
44 output line. */
45struct outlist
46 {
47 /* File number: 0, 1, or 2. 0 means use the join field.
48 1 means use the first file argument, 2 the second. */
49 int file;
50
51 /* Field index (zero-based), specified only when FILE is 1 or 2. */
52 size_t field;
53
54 struct outlist *next;
55 };
56
57/* A field of a line. */
58struct field
59 {
60 char *beg; /* First character in field. */
61 size_t len; /* The length of the field. */
62 };
63
64/* A line read from an input file. */
65struct line
66 {
67 struct linebuffer buf; /* The line itself. */
68 size_t nfields; /* Number of elements in `fields'. */
69 size_t nfields_allocated; /* Number of elements allocated for `fields'. */
70 struct field *fields;
71 };
72
73/* One or more consecutive lines read from a file that all have the
74 same join field value. */
75struct seq
76 {
77 size_t count; /* Elements used in `lines'. */
78 size_t alloc; /* Elements allocated in `lines'. */
79 struct line *lines;
80 };
81
82/* The name this program was run with. */
83char *program_name;
84
85/* True if the LC_COLLATE locale is hard. */
86static bool hard_LC_COLLATE;
87
88/* If nonzero, print unpairable lines in file 1 or 2. */
89static bool print_unpairables_1, print_unpairables_2;
90
91/* If nonzero, print pairable lines. */
92static bool print_pairables;
93
94/* Empty output field filler. */
95static char const *empty_filler;
96
97/* Field to join on; SIZE_MAX means they haven't been determined yet. */
98static size_t join_field_1 = SIZE_MAX;
99static size_t join_field_2 = SIZE_MAX;
100
101/* List of fields to print. */
102static struct outlist outlist_head;
103
104/* Last element in `outlist', where a new element can be added. */
105static struct outlist *outlist_end = &outlist_head;
106
107/* Tab character separating fields. If negative, fields are separated
108 by any nonempty string of blanks, otherwise by exactly one
109 tab character whose value (when cast to unsigned char) equals TAB. */
110static int tab = -1;
111
112static struct option const longopts[] =
113{
114 {"ignore-case", no_argument, NULL, 'i'},
115 {GETOPT_HELP_OPTION_DECL},
116 {GETOPT_VERSION_OPTION_DECL},
117 {NULL, 0, NULL, 0}
118};
119
120/* Used to print non-joining lines */
121static struct line uni_blank;
122
123/* If nonzero, ignore case when comparing join fields. */
124static bool ignore_case;
125
126void
127usage (int status)
128{
129 if (status != EXIT_SUCCESS)
130 fprintf (stderr, _("Try `%s --help' for more information.\n"),
131 program_name);
132 else
133 {
134 printf (_("\
135Usage: %s [OPTION]... FILE1 FILE2\n\
136"),
137 program_name);
138 fputs (_("\
139For each pair of input lines with identical join fields, write a line to\n\
140standard output. The default join field is the first, delimited\n\
141by whitespace. When FILE1 or FILE2 (not both) is -, read standard input.\n\
142\n\
143 -a FILENUM print unpairable lines coming from file FILENUM, where\n\
144 FILENUM is 1 or 2, corresponding to FILE1 or FILE2\n\
145 -e EMPTY replace missing input fields with EMPTY\n\
146"), stdout);
147 fputs (_("\
148 -i, --ignore-case ignore differences in case when comparing fields\n\
149 -j FIELD equivalent to `-1 FIELD -2 FIELD'\n\
150 -o FORMAT obey FORMAT while constructing output line\n\
151 -t CHAR use CHAR as input and output field separator\n\
152"), stdout);
153 fputs (_("\
154 -v FILENUM like -a FILENUM, but suppress joined output lines\n\
155 -1 FIELD join on this FIELD of file 1\n\
156 -2 FIELD join on this FIELD of file 2\n\
157"), stdout);
158 fputs (HELP_OPTION_DESCRIPTION, stdout);
159 fputs (VERSION_OPTION_DESCRIPTION, stdout);
160 fputs (_("\
161\n\
162Unless -t CHAR is given, leading blanks separate fields and are ignored,\n\
163else fields are separated by CHAR. Any FIELD is a field number counted\n\
164from 1. FORMAT is one or more comma or blank separated specifications,\n\
165each being `FILENUM.FIELD' or `0'. Default FORMAT outputs the join field,\n\
166the remaining fields from FILE1, the remaining fields from FILE2, all\n\
167separated by CHAR.\n\
168\n\
169Important: FILE1 and FILE2 must be sorted on the join fields.\n\
170"), stdout);
171 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
172 }
173 exit (status);
174}
175
176/* Return true if C is a blank (a default input field separator). */
177
178static inline bool
179is_blank (unsigned char c)
180{
181 return ISBLANK (c) != 0;
182}
183
184/* Record a field in LINE, with location FIELD and size LEN. */
185
186static void
187extract_field (struct line *line, char *field, size_t len)
188{
189 if (line->nfields >= line->nfields_allocated)
190 {
191 line->fields = X2NREALLOC (line->fields, &line->nfields_allocated);
192 }
193 line->fields[line->nfields].beg = field;
194 line->fields[line->nfields].len = len;
195 ++(line->nfields);
196}
197
198/* Fill in the `fields' structure in LINE. */
199
200static void
201xfields (struct line *line)
202{
203 char *ptr = line->buf.buffer;
204 char const *lim = ptr + line->buf.length - 1;
205
206 if (ptr == lim)
207 return;
208
209 if (0 <= tab)
210 {
211 char *sep;
212 for (; (sep = memchr (ptr, tab, lim - ptr)) != NULL; ptr = sep + 1)
213 extract_field (line, ptr, sep - ptr);
214 }
215 else
216 {
217 /* Skip leading blanks before the first field. */
218 while (is_blank (*ptr))
219 if (++ptr == lim)
220 return;
221
222 do
223 {
224 char *sep;
225 for (sep = ptr + 1; sep != lim && ! is_blank (*sep); sep++)
226 continue;
227 extract_field (line, ptr, sep - ptr);
228 if (sep == lim)
229 return;
230 for (ptr = sep + 1; ptr != lim && is_blank (*ptr); ptr++)
231 continue;
232 }
233 while (ptr != lim);
234 }
235
236 extract_field (line, ptr, lim - ptr);
237}
238
239/* Read a line from FP into LINE and split it into fields.
240 Return true if successful. */
241
242static bool
243get_line (FILE *fp, struct line *line)
244{
245 initbuffer (&line->buf);
246
247 if (! readlinebuffer (&line->buf, fp))
248 {
249 if (ferror (fp))
250 error (EXIT_FAILURE, errno, _("read error"));
251 free (line->buf.buffer);
252 line->buf.buffer = NULL;
253 return false;
254 }
255
256 line->nfields_allocated = 0;
257 line->nfields = 0;
258 line->fields = NULL;
259 xfields (line);
260 return true;
261}
262
263static void
264freeline (struct line *line)
265{
266 free (line->fields);
267 free (line->buf.buffer);
268 line->buf.buffer = NULL;
269}
270
271static void
272initseq (struct seq *seq)
273{
274 seq->count = 0;
275 seq->alloc = 0;
276 seq->lines = NULL;
277}
278
279/* Read a line from FP and add it to SEQ. Return true if successful. */
280
281static bool
282getseq (FILE *fp, struct seq *seq)
283{
284 if (seq->count == seq->alloc)
285 seq->lines = X2NREALLOC (seq->lines, &seq->alloc);
286
287 if (get_line (fp, &seq->lines[seq->count]))
288 {
289 ++seq->count;
290 return true;
291 }
292 return false;
293}
294
295static void
296delseq (struct seq *seq)
297{
298 size_t i;
299 for (i = 0; i < seq->count; i++)
300 if (seq->lines[i].buf.buffer)
301 freeline (&seq->lines[i]);
302 free (seq->lines);
303}
304
305/* Return <0 if the join field in LINE1 compares less than the one in LINE2;
306 >0 if it compares greater; 0 if it compares equal.
307 Report an error and exit if the comparison fails. */
308
309static int
310keycmp (struct line const *line1, struct line const *line2)
311{
312 /* Start of field to compare in each file. */
313 char *beg1;
314 char *beg2;
315
316 size_t len1;
317 size_t len2; /* Length of fields to compare. */
318 int diff;
319
320 if (join_field_1 < line1->nfields)
321 {
322 beg1 = line1->fields[join_field_1].beg;
323 len1 = line1->fields[join_field_1].len;
324 }
325 else
326 {
327 beg1 = NULL;
328 len1 = 0;
329 }
330
331 if (join_field_2 < line2->nfields)
332 {
333 beg2 = line2->fields[join_field_2].beg;
334 len2 = line2->fields[join_field_2].len;
335 }
336 else
337 {
338 beg2 = NULL;
339 len2 = 0;
340 }
341
342 if (len1 == 0)
343 return len2 == 0 ? 0 : -1;
344 if (len2 == 0)
345 return 1;
346
347 if (ignore_case)
348 {
349 /* FIXME: ignore_case does not work with NLS (in particular,
350 with multibyte chars). */
351 diff = memcasecmp (beg1, beg2, MIN (len1, len2));
352 }
353 else
354 {
355 if (hard_LC_COLLATE)
356 return xmemcoll (beg1, len1, beg2, len2);
357 diff = memcmp (beg1, beg2, MIN (len1, len2));
358 }
359
360 if (diff)
361 return diff;
362 return len1 < len2 ? -1 : len1 != len2;
363}
364
365/* Print field N of LINE if it exists and is nonempty, otherwise
366 `empty_filler' if it is nonempty. */
367
368static void
369prfield (size_t n, struct line const *line)
370{
371 size_t len;
372
373 if (n < line->nfields)
374 {
375 len = line->fields[n].len;
376 if (len)
377 fwrite (line->fields[n].beg, 1, len, stdout);
378 else if (empty_filler)
379 fputs (empty_filler, stdout);
380 }
381 else if (empty_filler)
382 fputs (empty_filler, stdout);
383}
384
385/* Print the join of LINE1 and LINE2. */
386
387static void
388prjoin (struct line const *line1, struct line const *line2)
389{
390 const struct outlist *outlist;
391 char output_separator = tab < 0 ? ' ' : tab;
392
393 outlist = outlist_head.next;
394 if (outlist)
395 {
396 const struct outlist *o;
397
398 o = outlist;
399 while (1)
400 {
401 size_t field;
402 struct line const *line;
403
404 if (o->file == 0)
405 {
406 if (line1 == &uni_blank)
407 {
408 line = line2;
409 field = join_field_2;
410 }
411 else
412 {
413 line = line1;
414 field = join_field_1;
415 }
416 }
417 else
418 {
419 line = (o->file == 1 ? line1 : line2);
420 field = o->field;
421 }
422 prfield (field, line);
423 o = o->next;
424 if (o == NULL)
425 break;
426 putchar (output_separator);
427 }
428 putchar ('\n');
429 }
430 else
431 {
432 size_t i;
433
434 if (line1 == &uni_blank)
435 {
436 struct line const *t;
437 t = line1;
438 line1 = line2;
439 line2 = t;
440 }
441 prfield (join_field_1, line1);
442 for (i = 0; i < join_field_1 && i < line1->nfields; ++i)
443 {
444 putchar (output_separator);
445 prfield (i, line1);
446 }
447 for (i = join_field_1 + 1; i < line1->nfields; ++i)
448 {
449 putchar (output_separator);
450 prfield (i, line1);
451 }
452
453 for (i = 0; i < join_field_2 && i < line2->nfields; ++i)
454 {
455 putchar (output_separator);
456 prfield (i, line2);
457 }
458 for (i = join_field_2 + 1; i < line2->nfields; ++i)
459 {
460 putchar (output_separator);
461 prfield (i, line2);
462 }
463 putchar ('\n');
464 }
465}
466
467/* Print the join of the files in FP1 and FP2. */
468
469static void
470join (FILE *fp1, FILE *fp2)
471{
472 struct seq seq1, seq2;
473 struct line line;
474 int diff;
475 bool eof1, eof2;
476
477 /* Read the first line of each file. */
478 initseq (&seq1);
479 getseq (fp1, &seq1);
480 initseq (&seq2);
481 getseq (fp2, &seq2);
482
483 while (seq1.count && seq2.count)
484 {
485 size_t i;
486 diff = keycmp (&seq1.lines[0], &seq2.lines[0]);
487 if (diff < 0)
488 {
489 if (print_unpairables_1)
490 prjoin (&seq1.lines[0], &uni_blank);
491 freeline (&seq1.lines[0]);
492 seq1.count = 0;
493 getseq (fp1, &seq1);
494 continue;
495 }
496 if (diff > 0)
497 {
498 if (print_unpairables_2)
499 prjoin (&uni_blank, &seq2.lines[0]);
500 freeline (&seq2.lines[0]);
501 seq2.count = 0;
502 getseq (fp2, &seq2);
503 continue;
504 }
505
506 /* Keep reading lines from file1 as long as they continue to
507 match the current line from file2. */
508 eof1 = false;
509 do
510 if (!getseq (fp1, &seq1))
511 {
512 eof1 = true;
513 ++seq1.count;
514 break;
515 }
516 while (!keycmp (&seq1.lines[seq1.count - 1], &seq2.lines[0]));
517
518 /* Keep reading lines from file2 as long as they continue to
519 match the current line from file1. */
520 eof2 = false;
521 do
522 if (!getseq (fp2, &seq2))
523 {
524 eof2 = true;
525 ++seq2.count;
526 break;
527 }
528 while (!keycmp (&seq1.lines[0], &seq2.lines[seq2.count - 1]));
529
530 if (print_pairables)
531 {
532 for (i = 0; i < seq1.count - 1; ++i)
533 {
534 size_t j;
535 for (j = 0; j < seq2.count - 1; ++j)
536 prjoin (&seq1.lines[i], &seq2.lines[j]);
537 }
538 }
539
540 for (i = 0; i < seq1.count - 1; ++i)
541 freeline (&seq1.lines[i]);
542 if (!eof1)
543 {
544 seq1.lines[0] = seq1.lines[seq1.count - 1];
545 seq1.count = 1;
546 }
547 else
548 seq1.count = 0;
549
550 for (i = 0; i < seq2.count - 1; ++i)
551 freeline (&seq2.lines[i]);
552 if (!eof2)
553 {
554 seq2.lines[0] = seq2.lines[seq2.count - 1];
555 seq2.count = 1;
556 }
557 else
558 seq2.count = 0;
559 }
560
561 if (print_unpairables_1 && seq1.count)
562 {
563 prjoin (&seq1.lines[0], &uni_blank);
564 freeline (&seq1.lines[0]);
565 while (get_line (fp1, &line))
566 {
567 prjoin (&line, &uni_blank);
568 freeline (&line);
569 }
570 }
571
572 if (print_unpairables_2 && seq2.count)
573 {
574 prjoin (&uni_blank, &seq2.lines[0]);
575 freeline (&seq2.lines[0]);
576 while (get_line (fp2, &line))
577 {
578 prjoin (&uni_blank, &line);
579 freeline (&line);
580 }
581 }
582
583 delseq (&seq1);
584 delseq (&seq2);
585}
586
587/* Add a field spec for field FIELD of file FILE to `outlist'. */
588
589static void
590add_field (int file, size_t field)
591{
592 struct outlist *o;
593
594 assert (file == 0 || file == 1 || file == 2);
595 assert (file != 0 || field == 0);
596
597 o = xmalloc (sizeof *o);
598 o->file = file;
599 o->field = field;
600 o->next = NULL;
601
602 /* Add to the end of the list so the fields are in the right order. */
603 outlist_end->next = o;
604 outlist_end = o;
605}
606
607/* Convert a string of decimal digits, STR (the 1-based join field number),
608 to an integral value. Upon successful conversion, return one less
609 (the zero-based field number). If it cannot be converted, give a
610 diagnostic and exit. */
611
612static size_t
613string_to_join_field (char const *str)
614{
615 size_t result;
616 unsigned long int val;
617
618 strtol_error s_err = xstrtoul (str, NULL, 10, &val, "");
619 if (s_err == LONGINT_OVERFLOW || (s_err == LONGINT_OK && SIZE_MAX < val))
620 {
621 error (EXIT_FAILURE, 0,
622 _("value %s is so large that it is not representable"),
623 quote (str));
624 }
625
626 if (s_err != LONGINT_OK || val == 0)
627 error (EXIT_FAILURE, 0, _("invalid field number: %s"), quote (str));
628
629 result = val - 1;
630
631 return result;
632}
633
634/* Convert a single field specifier string, S, to a *FILE_INDEX, *FIELD_INDEX
635 pair. In S, the field index string is 1-based; *FIELD_INDEX is zero-based.
636 If S is valid, return true. Otherwise, give a diagnostic and exit. */
637
638static void
639decode_field_spec (const char *s, int *file_index, size_t *field_index)
640{
641 /* The first character must be 0, 1, or 2. */
642 switch (s[0])
643 {
644 case '0':
645 if (s[1])
646 {
647 /* `0' must be all alone -- no `.FIELD'. */
648 error (EXIT_FAILURE, 0, _("invalid field specifier: %s"), quote (s));
649 }
650 *file_index = 0;
651 *field_index = 0;
652 break;
653
654 case '1':
655 case '2':
656 if (s[1] != '.')
657 error (EXIT_FAILURE, 0, _("invalid field specifier: %s"), quote (s));
658 *file_index = s[0] - '0';
659 *field_index = string_to_join_field (s + 2);
660 break;
661
662 default:
663 error (EXIT_FAILURE, 0,
664 _("invalid file number in field spec: %s"), quote (s));
665
666 /* Tell gcc -W -Wall that we can't get beyond this point.
667 This avoids a warning (otherwise legit) that the caller's copies
668 of *file_index and *field_index might be used uninitialized. */
669 abort ();
670
671 break;
672 }
673}
674
675/* Add the comma or blank separated field spec(s) in STR to `outlist'. */
676
677static void
678add_field_list (char *str)
679{
680 char *p = str;
681
682 do
683 {
684 int file_index;
685 size_t field_index;
686 char const *spec_item = p;
687
688 p = strpbrk (p, ", \t");
689 if (p)
690 *p++ = '\0';
691 decode_field_spec (spec_item, &file_index, &field_index);
692 add_field (file_index, field_index);
693 }
694 while (p);
695}
696
697/* Set the join field *VAR to VAL, but report an error if *VAR is set
698 more than once to incompatible values. */
699
700static void
701set_join_field (size_t *var, size_t val)
702{
703 if (*var != SIZE_MAX && *var != val)
704 {
705 unsigned long int var1 = *var + 1;
706 unsigned long int val1 = val + 1;
707 error (EXIT_FAILURE, 0, _("incompatible join fields %lu, %lu"),
708 var1, val1);
709 }
710 *var = val;
711}
712
713/* Status of command-line arguments. */
714
715enum operand_status
716 {
717 /* This argument must be an operand, i.e., one of the files to be
718 joined. */
719 MUST_BE_OPERAND,
720
721 /* This might be the argument of the preceding -j1 or -j2 option,
722 or it might be an operand. */
723 MIGHT_BE_J1_ARG,
724 MIGHT_BE_J2_ARG,
725
726 /* This might be the argument of the preceding -o option, or it might be
727 an operand. */
728 MIGHT_BE_O_ARG
729 };
730
731/* Add NAME to the array of input file NAMES with operand statuses
732 OPERAND_STATUS; currently there are NFILES names in the list. */
733
734static void
735add_file_name (char *name, char *names[2],
736 int operand_status[2], int joption_count[2], int *nfiles,
737 int *prev_optc_status, int *optc_status)
738{
739 int n = *nfiles;
740
741 if (n == 2)
742 {
743 bool op0 = (operand_status[0] == MUST_BE_OPERAND);
744 char *arg = names[op0];
745 switch (operand_status[op0])
746 {
747 case MUST_BE_OPERAND:
748 error (0, 0, _("extra operand %s"), quote (name));
749 usage (EXIT_FAILURE);
750
751 case MIGHT_BE_J1_ARG:
752 joption_count[0]--;
753 set_join_field (&join_field_1, string_to_join_field (arg));
754 break;
755
756 case MIGHT_BE_J2_ARG:
757 joption_count[1]--;
758 set_join_field (&join_field_2, string_to_join_field (arg));
759 break;
760
761 case MIGHT_BE_O_ARG:
762 add_field_list (arg);
763 break;
764 }
765 if (!op0)
766 {
767 operand_status[0] = operand_status[1];
768 names[0] = names[1];
769 }
770 n = 1;
771 }
772
773 operand_status[n] = *prev_optc_status;
774 names[n] = name;
775 *nfiles = n + 1;
776 if (*prev_optc_status == MIGHT_BE_O_ARG)
777 *optc_status = MIGHT_BE_O_ARG;
778}
779
780int
781main (int argc, char **argv)
782{
783 int optc_status;
784 int prev_optc_status = MUST_BE_OPERAND;
785 int operand_status[2];
786 int joption_count[2] = { 0, 0 };
787 char *names[2];
788 FILE *fp1, *fp2;
789 int optc;
790 int nfiles = 0;
791 int i;
792
793#ifdef __EMX__
794 /* a undocumented hack */
795 if (getenv("JOIN_BINARY_MODE"))
796 {
797 extern int _fmode_bin;
798 _fmode_bin = 1;
799 if (!isatty(fileno(stdout)))
800 freopen(NULL, "wb", stdout);
801 if (!isatty(fileno(stdin)))
802 freopen(NULL, "rb", stdin);
803 }
804#endif
805
806 initialize_main (&argc, &argv);
807 program_name = argv[0];
808 setlocale (LC_ALL, "");
809 bindtextdomain (PACKAGE, LOCALEDIR);
810 textdomain (PACKAGE);
811 hard_LC_COLLATE = hard_locale (LC_COLLATE);
812
813 atexit (close_stdout);
814
815 print_pairables = true;
816
817 while ((optc = getopt_long (argc, argv, "-a:e:i1:2:j:o:t:v:",
818 longopts, NULL))
819 != -1)
820 {
821 optc_status = MUST_BE_OPERAND;
822
823 switch (optc)
824 {
825 case 'v':
826 print_pairables = false;
827 /* Fall through. */
828
829 case 'a':
830 {
831 unsigned long int val;
832 if (xstrtoul (optarg, NULL, 10, &val, "") != LONGINT_OK
833 || (val != 1 && val != 2))
834 error (EXIT_FAILURE, 0,
835 _("invalid field number: %s"), quote (optarg));
836 if (val == 1)
837 print_unpairables_1 = true;
838 else
839 print_unpairables_2 = true;
840 }
841 break;
842
843 case 'e':
844 if (empty_filler && ! STREQ (empty_filler, optarg))
845 error (EXIT_FAILURE, 0,
846 _("conflicting empty-field replacement strings"));
847 empty_filler = optarg;
848 break;
849
850 case 'i':
851 ignore_case = true;
852 break;
853
854 case '1':
855 set_join_field (&join_field_1, string_to_join_field (optarg));
856 break;
857
858 case '2':
859 set_join_field (&join_field_2, string_to_join_field (optarg));
860 break;
861
862 case 'j':
863 if ((optarg[0] == '1' || optarg[0] == '2') && !optarg[1]
864 && optarg == argv[optind - 1] + 2)
865 {
866 /* The argument was either "-j1" or "-j2". */
867 bool is_j2 = (optarg[0] == '2');
868 joption_count[is_j2]++;
869 optc_status = MIGHT_BE_J1_ARG + is_j2;
870 }
871 else
872 {
873 set_join_field (&join_field_1, string_to_join_field (optarg));
874 set_join_field (&join_field_2, join_field_1);
875 }
876 break;
877
878 case 'o':
879 add_field_list (optarg);
880 optc_status = MIGHT_BE_O_ARG;
881 break;
882
883 case 't':
884 {
885 unsigned char newtab = optarg[0];
886 if (! newtab)
887 error (EXIT_FAILURE, 0, _("empty tab"));
888 if (optarg[1])
889 {
890 if (STREQ (optarg, "\\0"))
891 newtab = '\0';
892 else
893 error (EXIT_FAILURE, 0, _("multi-character tab %s"),
894 quote (optarg));
895 }
896 if (0 <= tab && tab != newtab)
897 error (EXIT_FAILURE, 0, _("incompatible tabs"));
898 tab = newtab;
899 }
900 break;
901
902 case 1: /* Non-option argument. */
903 add_file_name (optarg, names, operand_status, joption_count,
904 &nfiles, &prev_optc_status, &optc_status);
905 break;
906
907 case_GETOPT_HELP_CHAR;
908
909 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
910
911 default:
912 usage (EXIT_FAILURE);
913 }
914
915 prev_optc_status = optc_status;
916 }
917
918 /* Process any operands after "--". */
919 prev_optc_status = MUST_BE_OPERAND;
920 while (optind < argc)
921 add_file_name (argv[optind++], names, operand_status, joption_count,
922 &nfiles, &prev_optc_status, &optc_status);
923
924 if (nfiles != 2)
925 {
926 if (nfiles == 0)
927 error (0, 0, _("missing operand"));
928 else
929 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
930 usage (EXIT_FAILURE);
931 }
932
933 /* If "-j1" was specified and it turns out not to have had an argument,
934 treat it as "-j 1". Likewise for -j2. */
935 for (i = 0; i < 2; i++)
936 if (joption_count[i] != 0)
937 {
938 set_join_field (&join_field_1, i);
939 set_join_field (&join_field_2, i);
940 }
941
942 if (join_field_1 == SIZE_MAX)
943 join_field_1 = 0;
944 if (join_field_2 == SIZE_MAX)
945 join_field_2 = 0;
946
947 fp1 = STREQ (names[0], "-") ? stdin : fopen (names[0], "r");
948 if (!fp1)
949 error (EXIT_FAILURE, errno, "%s", names[0]);
950 fp2 = STREQ (names[1], "-") ? stdin : fopen (names[1], "r");
951 if (!fp2)
952 error (EXIT_FAILURE, errno, "%s", names[1]);
953 if (fp1 == fp2)
954 error (EXIT_FAILURE, errno, _("both files cannot be standard input"));
955 join (fp1, fp2);
956
957 if (fclose (fp1) != 0)
958 error (EXIT_FAILURE, errno, "%s", names[0]);
959 if (fclose (fp2) != 0)
960 error (EXIT_FAILURE, errno, "%s", names[1]);
961
962 exit (EXIT_SUCCESS);
963}
Note: See TracBrowser for help on using the repository browser.