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