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