1 /* comm -- compare two sorted files line by line.
2 Copyright (C) 1986-2022 Free Software Foundation, Inc.
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 3 of the License, or
7 (at your option) any later version.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Richard Stallman and David MacKenzie. */
22 #include <sys/types.h>
24 #include "linebuffer.h"
28 #include "hard-locale.h"
34 /* The official name of this program (e.g., no 'g' prefix). */
35 #define PROGRAM_NAME "comm"
38 proper_name ("Richard M. Stallman"), \
39 proper_name ("David MacKenzie")
41 /* Undefine, to avoid warning about redefinition on some systems. */
43 #define min(x, y) ((x) < (y) ? (x) : (y))
45 /* True if the LC_COLLATE locale is hard. */
46 static bool hard_LC_COLLATE
;
48 /* If true, print lines that are found only in file 1. */
49 static bool only_file_1
;
51 /* If true, print lines that are found only in file 2. */
52 static bool only_file_2
;
54 /* If true, print lines that are found in both files. */
57 /* If nonzero, we have seen at least one unpairable line. */
58 static bool seen_unpairable
;
60 /* If nonzero, we have warned about disorder in that file. */
61 static bool issued_disorder_warning
[2];
64 static unsigned char delim
= '\n';
66 /* If true, print a summary. */
67 static bool total_option
;
69 /* If nonzero, check that the input is correctly ordered. */
77 /* Output columns will be delimited with this string, which may be set
78 on the command-line with --output-delimiter=STR. */
79 static char const *col_sep
= "\t";
80 static size_t col_sep_len
= 0;
82 /* For long options that have no equivalent short option, use a
83 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
86 CHECK_ORDER_OPTION
= CHAR_MAX
+ 1,
88 OUTPUT_DELIMITER_OPTION
,
92 static struct option
const long_options
[] =
94 {"check-order", no_argument
, NULL
, CHECK_ORDER_OPTION
},
95 {"nocheck-order", no_argument
, NULL
, NOCHECK_ORDER_OPTION
},
96 {"output-delimiter", required_argument
, NULL
, OUTPUT_DELIMITER_OPTION
},
97 {"total", no_argument
, NULL
, TOTAL_OPTION
},
98 {"zero-terminated", no_argument
, NULL
, 'z'},
99 {GETOPT_HELP_OPTION_DECL
},
100 {GETOPT_VERSION_OPTION_DECL
},
108 if (status
!= EXIT_SUCCESS
)
113 Usage: %s [OPTION]... FILE1 FILE2\n\
117 Compare sorted files FILE1 and FILE2 line by line.\n\
121 When FILE1 or FILE2 (not both) is -, read standard input.\n\
125 With no options, produce three-column output. Column one contains\n\
126 lines unique to FILE1, column two contains lines unique to FILE2,\n\
127 and column three contains lines common to both files.\n\
131 -1 suppress column 1 (lines unique to FILE1)\n\
132 -2 suppress column 2 (lines unique to FILE2)\n\
133 -3 suppress column 3 (lines that appear in both files)\n\
137 --check-order check that the input is correctly sorted, even\n\
138 if all input lines are pairable\n\
139 --nocheck-order do not check that the input is correctly sorted\n\
142 --output-delimiter=STR separate columns with STR\n\
145 --total output a summary\n\
148 -z, --zero-terminated line delimiter is NUL, not newline\n\
150 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
151 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
154 Note, comparisons honor the rules specified by 'LC_COLLATE'.\n\
159 %s -12 file1 file2 Print only lines present in both file1 and file2.\n\
160 %s -3 file1 file2 Print lines in file1 not in file2, and vice versa.\n\
162 program_name
, program_name
);
163 emit_ancillary_info (PROGRAM_NAME
);
168 /* Output the line in linebuffer LINE to stream STREAM
169 provided the switches say it should be output.
170 CLASS is 1 for a line found only in file 1,
171 2 for a line only in file 2, 3 for a line in both. */
174 writeline (struct linebuffer
const *line
, FILE *stream
, int class)
187 fwrite (col_sep
, 1, col_sep_len
, stream
);
194 fwrite (col_sep
, 1, col_sep_len
, stream
);
196 fwrite (col_sep
, 1, col_sep_len
, stream
);
200 fwrite (line
->buffer
, sizeof (char), line
->length
, stream
);
203 /* Check that successive input lines PREV and CURRENT from input file
204 WHATFILE are presented in order.
206 If the user specified --nocheck-order, the check is not made.
207 If the user specified --check-order, the problem is fatal.
208 Otherwise (the default), the message is simply a warning.
210 A message is printed at most once per input file.
212 This function was copied (nearly) verbatim from 'src/join.c'. */
215 check_order (struct linebuffer
const *prev
,
216 struct linebuffer
const *current
,
220 if (check_input_order
!= CHECK_ORDER_DISABLED
221 && ((check_input_order
== CHECK_ORDER_ENABLED
) || seen_unpairable
))
223 if (!issued_disorder_warning
[whatfile
- 1])
228 order
= xmemcoll (prev
->buffer
, prev
->length
- 1,
229 current
->buffer
, current
->length
- 1);
231 order
= memcmp2 (prev
->buffer
, prev
->length
- 1,
232 current
->buffer
, current
->length
- 1);
236 error ((check_input_order
== CHECK_ORDER_ENABLED
238 0, _("file %d is not in sorted order"), whatfile
);
240 /* If we get to here, the message was just a warning, but we
241 want only to issue it once. */
242 issued_disorder_warning
[whatfile
- 1] = true;
248 /* Compare INFILES[0] and INFILES[1].
249 If either is "-", use the standard input for that file.
250 Assume that each input file is sorted;
251 merge them and output the result.
252 Exit the program when done. */
254 static _Noreturn
void
255 compare_files (char **infiles
)
257 /* For each file, we have four linebuffers in lba. */
258 struct linebuffer lba
[2][4];
260 /* thisline[i] points to the linebuffer holding the next available line
261 in file i, or is NULL if there are no lines left in that file. */
262 struct linebuffer
*thisline
[2];
264 /* all_line[i][alt[i][0]] also points to the linebuffer holding the
265 current line in file i. We keep two buffers of history around so we
266 can look two lines back when we get to the end of a file. */
267 struct linebuffer
*all_line
[2][4];
269 /* This is used to rotate through the buffers for each input file. */
272 /* streams[i] holds the input stream for file i. */
275 /* Counters for the summary. */
276 uintmax_t total
[] = {0, 0, 0};
280 /* Initialize the storage. */
281 for (i
= 0; i
< 2; i
++)
283 for (j
= 0; j
< 4; j
++)
285 initbuffer (&lba
[i
][j
]);
286 all_line
[i
][j
] = &lba
[i
][j
];
291 streams
[i
] = (STREQ (infiles
[i
], "-") ? stdin
: fopen (infiles
[i
], "r"));
293 die (EXIT_FAILURE
, errno
, "%s", quotef (infiles
[i
]));
295 fadvise (streams
[i
], FADVISE_SEQUENTIAL
);
297 thisline
[i
] = readlinebuffer_delim (all_line
[i
][alt
[i
][0]], streams
[i
],
299 if (ferror (streams
[i
]))
300 die (EXIT_FAILURE
, errno
, "%s", quotef (infiles
[i
]));
303 while (thisline
[0] || thisline
[1])
306 bool fill_up
[2] = { false, false };
308 /* Compare the next available lines of the two files. */
312 else if (!thisline
[1])
317 order
= xmemcoll (thisline
[0]->buffer
, thisline
[0]->length
- 1,
318 thisline
[1]->buffer
, thisline
[1]->length
- 1);
321 size_t len
= min (thisline
[0]->length
, thisline
[1]->length
) - 1;
322 order
= memcmp (thisline
[0]->buffer
, thisline
[1]->buffer
, len
);
324 order
= ((thisline
[0]->length
> thisline
[1]->length
)
325 - (thisline
[0]->length
< thisline
[1]->length
));
329 /* Output the line that is lesser. */
332 /* Line is seen in both files. */
334 writeline (thisline
[1], stdout
, 3);
338 seen_unpairable
= true;
341 /* Line is seen in file 1 only. */
343 writeline (thisline
[0], stdout
, 1);
347 /* Line is seen in file 2 only. */
349 writeline (thisline
[1], stdout
, 2);
353 /* Step the file the line came from.
354 If the files match, step both files. */
360 for (i
= 0; i
< 2; i
++)
363 /* Rotate the buffers for this file. */
364 alt
[i
][2] = alt
[i
][1];
365 alt
[i
][1] = alt
[i
][0];
366 alt
[i
][0] = (alt
[i
][0] + 1) & 0x03;
368 thisline
[i
] = readlinebuffer_delim (all_line
[i
][alt
[i
][0]],
372 check_order (all_line
[i
][alt
[i
][1]], thisline
[i
], i
+ 1);
374 /* If this is the end of the file we may need to re-check
375 the order of the previous two lines, since we might have
376 discovered an unpairable match since we checked before. */
377 else if (all_line
[i
][alt
[i
][2]]->buffer
)
378 check_order (all_line
[i
][alt
[i
][2]],
379 all_line
[i
][alt
[i
][1]], i
+ 1);
381 if (ferror (streams
[i
]))
382 die (EXIT_FAILURE
, errno
, "%s", quotef (infiles
[i
]));
388 for (i
= 0; i
< 2; i
++)
389 if (fclose (streams
[i
]) != 0)
390 die (EXIT_FAILURE
, errno
, "%s", quotef (infiles
[i
]));
394 /* Print the summary, minding the column and line delimiters. */
395 char buf1
[INT_BUFSIZE_BOUND (uintmax_t)];
396 char buf2
[INT_BUFSIZE_BOUND (uintmax_t)];
397 char buf3
[INT_BUFSIZE_BOUND (uintmax_t)];
398 printf ("%s%s%s%s%s%s%s%c",
399 umaxtostr (total
[0], buf1
), col_sep
,
400 umaxtostr (total
[1], buf2
), col_sep
,
401 umaxtostr (total
[2], buf3
), col_sep
,
405 if (issued_disorder_warning
[0] || issued_disorder_warning
[1])
406 die (EXIT_FAILURE
, 0, _("input is not in sorted order"));
408 /* Exit here to pacify gcc -fsanitizer=leak. */
413 main (int argc
, char **argv
)
417 initialize_main (&argc
, &argv
);
418 set_program_name (argv
[0]);
419 setlocale (LC_ALL
, "");
420 bindtextdomain (PACKAGE
, LOCALEDIR
);
421 textdomain (PACKAGE
);
422 hard_LC_COLLATE
= hard_locale (LC_COLLATE
);
424 atexit (close_stdout
);
430 seen_unpairable
= false;
431 issued_disorder_warning
[0] = issued_disorder_warning
[1] = false;
432 check_input_order
= CHECK_ORDER_DEFAULT
;
433 total_option
= false;
435 while ((c
= getopt_long (argc
, argv
, "123z", long_options
, NULL
)) != -1)
454 case NOCHECK_ORDER_OPTION
:
455 check_input_order
= CHECK_ORDER_DISABLED
;
458 case CHECK_ORDER_OPTION
:
459 check_input_order
= CHECK_ORDER_ENABLED
;
462 case OUTPUT_DELIMITER_OPTION
:
463 if (col_sep_len
&& !STREQ (col_sep
, optarg
))
464 die (EXIT_FAILURE
, 0, _("multiple output delimiters specified"));
466 col_sep_len
= *optarg
? strlen (optarg
) : 1;
473 case_GETOPT_HELP_CHAR
;
475 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
478 usage (EXIT_FAILURE
);
484 if (argc
- optind
< 2)
487 error (0, 0, _("missing operand"));
489 error (0, 0, _("missing operand after %s"), quote (argv
[argc
- 1]));
490 usage (EXIT_FAILURE
);
493 if (2 < argc
- optind
)
495 error (0, 0, _("extra operand %s"), quote (argv
[optind
+ 2]));
496 usage (EXIT_FAILURE
);
499 compare_files (argv
+ optind
);