1 /* comm -- compare two sorted files line by line.
2 Copyright (C) 86, 90, 91, 1995-2005, 2008 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 <http://www.gnu.org/licenses/>. */
17 /* Written by Richard Stallman and David MacKenzie. */
22 #include <sys/types.h>
24 #include "linebuffer.h"
26 #include "hard-locale.h"
31 /* The official name of this program (e.g., no `g' prefix). */
32 #define PROGRAM_NAME "comm"
35 proper_name ("Richard M. Stallman"), \
36 proper_name ("David MacKenzie")
38 /* Undefine, to avoid warning about redefinition on some systems. */
40 #define min(x, y) ((x) < (y) ? (x) : (y))
42 /* True if the LC_COLLATE locale is hard. */
43 static bool hard_LC_COLLATE;
45 /* If true, print lines that are found only in file 1. */
46 static bool only_file_1;
48 /* If true, print lines that are found only in file 2. */
49 static bool only_file_2;
51 /* If true, print lines that are found in both files. */
54 /* If nonzero, we have seen at least one unpairable line. */
55 static bool seen_unpairable;
57 /* If nonzero, we have warned about disorder in that file. */
58 static bool issued_disorder_warning[2];
60 /* If nonzero, check that the input is correctly ordered. */
68 /* Output columns will be delimited with this string, which may be set
69 on the command-line with --output-delimiter=STR. The default is a
70 single TAB character. */
71 static char const *delimiter;
73 /* For long options that have no equivalent short option, use a
74 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
77 CHECK_ORDER_OPTION = CHAR_MAX + 1,
79 OUTPUT_DELIMITER_OPTION
82 static struct option const long_options[] =
84 {"check-order", no_argument, NULL, CHECK_ORDER_OPTION},
85 {"nocheck-order", no_argument, NULL, NOCHECK_ORDER_OPTION},
86 {"output-delimiter", required_argument, NULL, OUTPUT_DELIMITER_OPTION},
87 {GETOPT_HELP_OPTION_DECL},
88 {GETOPT_VERSION_OPTION_DECL},
97 if (status != EXIT_SUCCESS)
98 fprintf (stderr, _("Try `%s --help' for more information.\n"),
103 Usage: %s [OPTION]... FILE1 FILE2\n\
107 Compare sorted files FILE1 and FILE2 line by line.\n\
111 With no options, produce three-column output. Column one contains\n\
112 lines unique to FILE1, column two contains lines unique to FILE2,\n\
113 and column three contains lines common to both files.\n\
117 -1 suppress lines unique to FILE1\n\
118 -2 suppress lines unique to FILE2\n\
119 -3 suppress lines that appear in both files\n\
123 --check-order check that the input is correctly sorted, even\n\
124 if all input lines are pairable\n\
125 --nocheck-order do not check that the input is correctly sorted\n\
128 --output-delimiter=STR separate columns with STR\n\
130 fputs (HELP_OPTION_DESCRIPTION, stdout);
131 fputs (VERSION_OPTION_DESCRIPTION, stdout);
132 emit_bug_reporting_address ();
137 /* Output the line in linebuffer LINE to stream STREAM
138 provided the switches say it should be output.
139 CLASS is 1 for a line found only in file 1,
140 2 for a line only in file 2, 3 for a line in both. */
143 writeline (struct linebuffer const *line, FILE *stream, int class)
155 /* Print a delimiter if we are printing lines from file 1. */
157 fputs (delimiter, stream);
163 /* Print a delimiter if we are printing lines from file 1. */
165 fputs (delimiter, stream);
166 /* Print a delimiter if we are printing lines from file 2. */
168 fputs (delimiter, stream);
172 fwrite (line->buffer, sizeof (char), line->length, stream);
175 /* Check that successive input lines PREV and CURRENT from input file
176 WHATFILE are presented in order.
178 If the user specified --nocheck-order, the check is not made.
179 If the user specified --check-order, the problem is fatal.
180 Otherwise (the default), the message is simply a warning.
182 A message is printed at most once per input file.
184 This funtion was copied (nearly) verbatim from `src/join.c'. */
187 check_order (struct linebuffer const *prev,
188 struct linebuffer const *current,
192 if (check_input_order != CHECK_ORDER_DISABLED
193 && ((check_input_order == CHECK_ORDER_ENABLED) || seen_unpairable))
195 if (!issued_disorder_warning[whatfile - 1])
200 order = xmemcoll (prev->buffer, prev->length - 1,
201 current->buffer, current->length - 1);
204 size_t len = min (prev->length, current->length) - 1;
205 order = memcmp (prev->buffer, current->buffer, len);
210 error ((check_input_order == CHECK_ORDER_ENABLED
212 0, _("file %d is not in sorted order"), whatfile);
214 /* If we get to here, the message was just a warning, but we
215 want only to issue it once. */
216 issued_disorder_warning[whatfile - 1] = true;
222 /* Compare INFILES[0] and INFILES[1].
223 If either is "-", use the standard input for that file.
224 Assume that each input file is sorted;
225 merge them and output the result. */
228 compare_files (char **infiles)
230 /* For each file, we have four linebuffers in lba. */
231 struct linebuffer lba[2][4];
233 /* thisline[i] points to the linebuffer holding the next available line
234 in file i, or is NULL if there are no lines left in that file. */
235 struct linebuffer *thisline[2];
237 /* all_line[i][alt[i][0]] also points to the linebuffer holding the
238 current line in file i. We keep two buffers of history around so we
239 can look two lines back when we get to the end of a file. */
240 struct linebuffer *all_line[2][4];
242 /* This is used to rotate through the buffers for each input file. */
245 /* streams[i] holds the input stream for file i. */
250 /* Initialize the storage. */
251 for (i = 0; i < 2; i++)
253 for (j = 0; j < 4; j++)
255 initbuffer (&lba[i][j]);
256 all_line[i][j] = &lba[i][j];
261 streams[i] = (STREQ (infiles[i], "-") ? stdin : fopen (infiles[i], "r"));
263 error (EXIT_FAILURE, errno, "%s", infiles[i]);
265 thisline[i] = readlinebuffer (all_line[i][alt[i][0]], streams[i]);
266 if (ferror (streams[i]))
267 error (EXIT_FAILURE, errno, "%s", infiles[i]);
270 while (thisline[0] || thisline[1])
273 bool fill_up[2] = { false, false };
275 /* Compare the next available lines of the two files. */
279 else if (!thisline[1])
284 order = xmemcoll (thisline[0]->buffer, thisline[0]->length - 1,
285 thisline[1]->buffer, thisline[1]->length - 1);
288 size_t len = min (thisline[0]->length, thisline[1]->length) - 1;
289 order = memcmp (thisline[0]->buffer, thisline[1]->buffer, len);
291 order = (thisline[0]->length < thisline[1]->length
293 : thisline[0]->length != thisline[1]->length);
297 /* Output the line that is lesser. */
299 writeline (thisline[1], stdout, 3);
302 seen_unpairable = true;
304 writeline (thisline[0], stdout, 1);
306 writeline (thisline[1], stdout, 2);
309 /* Step the file the line came from.
310 If the files match, step both files. */
316 for (i = 0; i < 2; i++)
319 /* Rotate the buffers for this file. */
320 alt[i][2] = alt[i][1];
321 alt[i][1] = alt[i][0];
322 alt[i][0] = (alt[i][0] + 1) & 0x03;
324 thisline[i] = readlinebuffer (all_line[i][alt[i][0]], streams[i]);
327 check_order (all_line[i][alt[i][1]], thisline[i], i + 1);
329 /* If this is the end of the file we may need to re-check
330 the order of the previous two lines, since we might have
331 discovered an unpairable match since we checked before. */
332 else if (all_line[i][alt[i][2]]->buffer)
333 check_order (all_line[i][alt[i][2]],
334 all_line[i][alt[i][1]], i + 1);
336 if (ferror (streams[i]))
337 error (EXIT_FAILURE, errno, "%s", infiles[i]);
343 for (i = 0; i < 2; i++)
344 if (fclose (streams[i]) != 0)
345 error (EXIT_FAILURE, errno, "%s", infiles[i]);
349 main (int argc, char **argv)
353 initialize_main (&argc, &argv);
354 set_program_name (argv[0]);
355 setlocale (LC_ALL, "");
356 bindtextdomain (PACKAGE, LOCALEDIR);
357 textdomain (PACKAGE);
358 hard_LC_COLLATE = hard_locale (LC_COLLATE);
360 atexit (close_stdout);
366 seen_unpairable = false;
367 issued_disorder_warning[0] = issued_disorder_warning[1] = false;
368 check_input_order = CHECK_ORDER_DEFAULT;
370 while ((c = getopt_long (argc, argv, "123", long_options, NULL)) != -1)
385 case NOCHECK_ORDER_OPTION:
386 check_input_order = CHECK_ORDER_DISABLED;
389 case CHECK_ORDER_OPTION:
390 check_input_order = CHECK_ORDER_ENABLED;
393 case OUTPUT_DELIMITER_OPTION:
394 if (delimiter && !STREQ (delimiter, optarg))
395 error (EXIT_FAILURE, 0, _("multiple delimiters specified"));
399 error (EXIT_FAILURE, 0, _("empty %s not allowed"),
400 quote ("--output-delimiter"));
404 case_GETOPT_HELP_CHAR;
406 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
409 usage (EXIT_FAILURE);
412 if (argc - optind < 2)
415 error (0, 0, _("missing operand"));
417 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
418 usage (EXIT_FAILURE);
421 if (2 < argc - optind)
423 error (0, 0, _("extra operand %s"), quote (argv[optind + 2]));
424 usage (EXIT_FAILURE);
427 /* The default delimiter is a TAB. */
431 compare_files (argv + optind);
433 if (issued_disorder_warning[0] || issued_disorder_warning[1])