85c45e2008e58bb6092e24a79a80e4602a817914
[platform/upstream/coreutils.git] / src / uniq.c
1 /* uniq -- remove duplicate lines from a sorted file
2    Copyright (C) 86, 91, 1995-2003, 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 Richard Stallman and David MacKenzie. */
19 \f
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <getopt.h>
24 #include <sys/types.h>
25
26 #include "system.h"
27 #include "argmatch.h"
28 #include "linebuffer.h"
29 #include "error.h"
30 #include "hard-locale.h"
31 #include "posixver.h"
32 #include "xmemcoll.h"
33 #include "xstrtol.h"
34 #include "memcasecmp.h"
35
36 /* The official name of this program (e.g., no `g' prefix).  */
37 #define PROGRAM_NAME "uniq"
38
39 #define WRITTEN_BY _("Written by Richard Stallman and David MacKenzie.")
40
41 #define SWAP_LINES(A, B)                        \
42   do                                            \
43     {                                           \
44       struct linebuffer *_tmp;                  \
45       _tmp = (A);                               \
46       (A) = (B);                                \
47       (B) = _tmp;                               \
48     }                                           \
49   while (0)
50
51 /* The name this program was run with. */
52 char *program_name;
53
54 /* Nonzero if the LC_COLLATE locale is hard.  */
55 static int hard_LC_COLLATE;
56
57 /* Number of fields to skip on each line when doing comparisons. */
58 static size_t skip_fields;
59
60 /* Number of chars to skip after skipping any fields. */
61 static size_t skip_chars;
62
63 /* Number of chars to compare. */
64 static size_t check_chars;
65
66 enum countmode
67 {
68   count_occurrences,            /* -c Print count before output lines. */
69   count_none                    /* Default.  Do not print counts. */
70 };
71
72 /* Whether and how to precede the output lines with a count of the number of
73    times they occurred in the input. */
74 static enum countmode countmode;
75
76 /* Which lines to output: unique lines, the first of a group of
77    repeated lines, and the second and subsequented of a group of
78    repeated lines.  */
79 static bool output_unique;
80 static bool output_first_repeated;
81 static bool output_later_repeated;
82
83 /* If nonzero, ignore case when comparing.  */
84 static int ignore_case;
85
86 enum delimit_method
87 {
88   /* No delimiters output.  --all-repeated[=none] */
89   DM_NONE,
90
91   /* Delimiter precedes all groups.  --all-repeated=prepend */
92   DM_PREPEND,
93
94   /* Delimit all groups.  --all-repeated=separate */
95   DM_SEPARATE
96 };
97
98 static char const *const delimit_method_string[] =
99 {
100   "none", "prepend", "separate", 0
101 };
102
103 static enum delimit_method const delimit_method_map[] =
104 {
105   DM_NONE, DM_PREPEND, DM_SEPARATE
106 };
107
108 /* Select whether/how to delimit groups of duplicate lines.  */
109 static enum delimit_method delimit_groups;
110
111 static struct option const longopts[] =
112 {
113   {"count", no_argument, NULL, 'c'},
114   {"repeated", no_argument, NULL, 'd'},
115   {"all-repeated", optional_argument, NULL, 'D'},
116   {"ignore-case", no_argument, NULL, 'i'},
117   {"unique", no_argument, NULL, 'u'},
118   {"skip-fields", required_argument, NULL, 'f'},
119   {"skip-chars", required_argument, NULL, 's'},
120   {"check-chars", required_argument, NULL, 'w'},
121   {GETOPT_HELP_OPTION_DECL},
122   {GETOPT_VERSION_OPTION_DECL},
123   {NULL, 0, NULL, 0}
124 };
125
126 void
127 usage (int status)
128 {
129   if (status != 0)
130     fprintf (stderr, _("Try `%s --help' for more information.\n"),
131              program_name);
132   else
133     {
134       printf (_("\
135 Usage: %s [OPTION]... [INPUT [OUTPUT]]\n\
136 "),
137               program_name);
138       fputs (_("\
139 Discard all but one of successive identical lines from INPUT (or\n\
140 standard input), writing to OUTPUT (or standard output).\n\
141 \n\
142 "), stdout);
143      fputs (_("\
144 Mandatory arguments to long options are mandatory for short options too.\n\
145 "), stdout);
146      fputs (_("\
147   -c, --count           prefix lines by the number of occurrences\n\
148   -d, --repeated        only print duplicate lines\n\
149 "), stdout);
150      fputs (_("\
151   -D, --all-repeated[=delimit-method] print all duplicate lines\n\
152                         delimit-method={none(default),prepend,separate}\n\
153                         Delimiting is done with blank lines.\n\
154   -f, --skip-fields=N   avoid comparing the first N fields\n\
155   -i, --ignore-case     ignore differences in case when comparing\n\
156   -s, --skip-chars=N    avoid comparing the first N characters\n\
157   -u, --unique          only print unique lines\n\
158 "), stdout);
159      fputs (_("\
160   -w, --check-chars=N   compare no more than N characters in lines\n\
161 "), stdout);
162      fputs (HELP_OPTION_DESCRIPTION, stdout);
163      fputs (VERSION_OPTION_DESCRIPTION, stdout);
164      fputs (_("\
165 \n\
166 A field is a run of whitespace, then non-whitespace characters.\n\
167 Fields are skipped before chars.\n\
168 "), stdout);
169       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
170     }
171   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
172 }
173
174 /* Convert OPT to size_t, reporting an error using MSGID if it does
175    not fit.  */
176
177 static size_t
178 size_opt (char const *opt, char const *msgid)
179 {
180   unsigned long int size;
181   if (xstrtoul (opt, NULL, 10, &size, "") != LONGINT_OK
182       || SIZE_MAX < size)
183     error (EXIT_FAILURE, 0, "%s: %s", opt, _(msgid));
184   return size;
185 }
186
187 /* Given a linebuffer LINE,
188    return a pointer to the beginning of the line's field to be compared. */
189
190 static char *
191 find_field (const struct linebuffer *line)
192 {
193   register size_t count;
194   register char *lp = line->buffer;
195   register size_t size = line->length - 1;
196   register size_t i = 0;
197
198   for (count = 0; count < skip_fields && i < size; count++)
199     {
200       while (i < size && ISBLANK (lp[i]))
201         i++;
202       while (i < size && !ISBLANK (lp[i]))
203         i++;
204     }
205
206   for (count = 0; count < skip_chars && i < size; count++)
207     i++;
208
209   return lp + i;
210 }
211
212 /* Return zero if two strings OLD and NEW match, nonzero if not.
213    OLD and NEW point not to the beginnings of the lines
214    but rather to the beginnings of the fields to compare.
215    OLDLEN and NEWLEN are their lengths. */
216
217 static int
218 different (char *old, char *new, size_t oldlen, size_t newlen)
219 {
220   if (check_chars < oldlen)
221     oldlen = check_chars;
222   if (check_chars < newlen)
223     newlen = check_chars;
224
225   if (ignore_case)
226     {
227       /* FIXME: This should invoke strcoll somehow.  */
228       return oldlen != newlen || memcasecmp (old, new, oldlen);
229     }
230   else if (HAVE_SETLOCALE && hard_LC_COLLATE)
231     return xmemcoll (old, oldlen, new, newlen);
232   else
233     return oldlen != newlen || memcmp (old, new, oldlen);
234 }
235
236 /* Output the line in linebuffer LINE to stream STREAM
237    provided that the switches say it should be output.
238    MATCH is true if the line matches the previous line.
239    If requested, print the number of times it occurred, as well;
240    LINECOUNT + 1 is the number of times that the line occurred. */
241
242 static void
243 writeline (struct linebuffer const *line, FILE *stream,
244            bool match, int linecount)
245 {
246   if (! (linecount == 0 ? output_unique
247          : !match ? output_first_repeated
248          : output_later_repeated))
249     return;
250
251   if (countmode == count_occurrences)
252     fprintf (stream, "%7d ", linecount + 1);
253
254   fwrite (line->buffer, sizeof (char), line->length, stream);
255 }
256
257 /* Process input file INFILE with output to OUTFILE.
258    If either is "-", use the standard I/O stream for it instead. */
259
260 static void
261 check_file (const char *infile, const char *outfile)
262 {
263   FILE *istream;
264   FILE *ostream;
265   struct linebuffer lb1, lb2;
266   struct linebuffer *thisline, *prevline;
267
268   if (STREQ (infile, "-"))
269     istream = stdin;
270   else
271     istream = fopen (infile, "r");
272   if (istream == NULL)
273     error (EXIT_FAILURE, errno, "%s", infile);
274
275   if (STREQ (outfile, "-"))
276     ostream = stdout;
277   else
278     ostream = fopen (outfile, "w");
279   if (ostream == NULL)
280     error (EXIT_FAILURE, errno, "%s", outfile);
281
282   thisline = &lb1;
283   prevline = &lb2;
284
285   initbuffer (thisline);
286   initbuffer (prevline);
287
288   /* The duplication in the following `if' and `else' blocks is an
289      optimization to distinguish the common case (in which none of
290      the following options has been specified: --count, -repeated,
291      --all-repeated, --unique) from the others.  In the common case,
292      this optimization lets uniq output each different line right away,
293      without waiting to see if the next one is different.  */
294
295   if (output_unique && output_first_repeated && countmode == count_none)
296     {
297       char *prevfield IF_LINT (= NULL);
298       size_t prevlen IF_LINT (= 0);
299
300       while (!feof (istream))
301         {
302           char *thisfield;
303           size_t thislen;
304           if (readlinebuffer (thisline, istream) == 0)
305             break;
306           thisfield = find_field (thisline);
307           thislen = thisline->length - 1 - (thisfield - thisline->buffer);
308           if (prevline->length == 0
309               || different (thisfield, prevfield, thislen, prevlen))
310             {
311               fwrite (thisline->buffer, sizeof (char),
312                       thisline->length, ostream);
313
314               SWAP_LINES (prevline, thisline);
315               prevfield = thisfield;
316               prevlen = thislen;
317             }
318         }
319     }
320   else
321     {
322       char *prevfield;
323       size_t prevlen;
324       int match_count = 0;
325       int first_delimiter = 1;
326
327       if (readlinebuffer (prevline, istream) == 0)
328         goto closefiles;
329       prevfield = find_field (prevline);
330       prevlen = prevline->length - 1 - (prevfield - prevline->buffer);
331
332       while (!feof (istream))
333         {
334           bool match;
335           char *thisfield;
336           size_t thislen;
337           if (readlinebuffer (thisline, istream) == 0)
338             {
339               if (ferror (istream))
340                 goto closefiles;
341               break;
342             }
343           thisfield = find_field (thisline);
344           thislen = thisline->length - 1 - (thisfield - thisline->buffer);
345           match = !different (thisfield, prevfield, thislen, prevlen);
346
347           if (match)
348             ++match_count;
349
350           if (delimit_groups != DM_NONE)
351             {
352               if (!match)
353                 {
354                   if (match_count) /* a previous match */
355                     first_delimiter = 0; /* Only used when DM_SEPARATE */
356                 }
357               else if (match_count == 1)
358                 {
359                   if ((delimit_groups == DM_PREPEND)
360                       || (delimit_groups == DM_SEPARATE
361                           && !first_delimiter))
362                     putc ('\n', ostream);
363                 }
364             }
365
366           if (!match || output_later_repeated)
367             {
368               writeline (prevline, ostream, match, match_count);
369               SWAP_LINES (prevline, thisline);
370               prevfield = thisfield;
371               prevlen = thislen;
372               if (!match)
373                 match_count = 0;
374             }
375         }
376
377       writeline (prevline, ostream, false, match_count);
378     }
379
380  closefiles:
381   if (ferror (istream) || fclose (istream) == EOF)
382     error (EXIT_FAILURE, errno, _("error reading %s"), infile);
383
384   if (ferror (ostream))
385     error (EXIT_FAILURE, 0, _("error writing %s"), outfile);
386   /* Close ostream only if it's not stdout -- the latter is closed
387      via the atexit-invoked close_stdout.  */
388   if (ostream != stdout && fclose (ostream) != 0)
389     error (EXIT_FAILURE, errno, _("error writing %s"), outfile);
390
391   free (lb1.buffer);
392   free (lb2.buffer);
393 }
394
395 int
396 main (int argc, char **argv)
397 {
398   int optc = 0;
399   bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
400   bool obsolete_skip_fields = false;
401   int nfiles = 0;
402   char const *file[2];
403
404   file[0] = file[1] = "-";
405   initialize_main (&argc, &argv);
406   program_name = argv[0];
407   setlocale (LC_ALL, "");
408   bindtextdomain (PACKAGE, LOCALEDIR);
409   textdomain (PACKAGE);
410   hard_LC_COLLATE = hard_locale (LC_COLLATE);
411
412   atexit (close_stdout);
413
414   skip_chars = 0;
415   skip_fields = 0;
416   check_chars = SIZE_MAX;
417   output_unique = output_first_repeated = true;
418   output_later_repeated = false;
419   countmode = count_none;
420   delimit_groups = DM_NONE;
421
422   for (;;)
423     {
424       /* Parse an operand with leading "+" as a file after "--" was
425          seen; or if pedantic and a file was seen; or if not
426          obsolete.  */
427
428       if (optc == -1
429           || (posixly_correct && nfiles != 0)
430           || ((optc = getopt_long (argc, argv,
431                                    "-0123456789Dcdf:is:uw:", longopts, NULL))
432               == -1))
433         {
434           if (argc <= optind)
435             break;
436           if (nfiles == 2)
437             {
438               error (0, 0, _("extra operand `%s'"), argv[optind]);
439               usage (EXIT_FAILURE);
440             }
441           file[nfiles++] = argv[optind++];
442         }
443       else switch (optc)
444         {
445         case 1:
446           {
447             unsigned long int size;
448             if (optarg[0] == '+'
449                 && posix2_version () < 200112
450                 && xstrtoul (optarg, NULL, 10, &size, "") == LONGINT_OK
451                 && size <= SIZE_MAX)
452               skip_chars = size;
453             else if (nfiles == 2)
454               {
455                 error (0, 0, _("extra operand `%s'"), optarg);
456                 usage (EXIT_FAILURE);
457               }
458             else
459               file[nfiles++] = optarg;
460           }
461           break;
462
463         case '0':
464         case '1':
465         case '2':
466         case '3':
467         case '4':
468         case '5':
469         case '6':
470         case '7':
471         case '8':
472         case '9':
473           {
474             size_t s = skip_fields;
475             skip_fields = s * 10 + optc - '0';
476             if (SIZE_MAX / 10 < s || skip_fields < s)
477               error (EXIT_FAILURE, 0, "%s",
478                      _("invalid number of fields to skip"));
479             obsolete_skip_fields = true;
480           }
481           break;
482
483         case 'c':
484           countmode = count_occurrences;
485           break;
486
487         case 'd':
488           output_unique = false;
489           break;
490
491         case 'D':
492           output_unique = false;
493           output_later_repeated = true;
494           if (optarg == NULL)
495             delimit_groups = DM_NONE;
496           else
497             delimit_groups = XARGMATCH ("--all-repeated", optarg,
498                                         delimit_method_string,
499                                         delimit_method_map);
500           break;
501
502         case 'f':               /* Like '-#'. */
503           skip_fields = size_opt (optarg,
504                                   N_("invalid number of fields to skip"));
505           break;
506
507         case 'i':
508           ignore_case = 1;
509           break;
510
511         case 's':               /* Like '+#'. */
512           skip_chars = size_opt (optarg,
513                                  N_("invalid number of bytes to skip"));
514           break;
515
516         case 'u':
517           output_first_repeated = false;
518           break;
519
520         case 'w':
521           check_chars = size_opt (optarg,
522                                   N_("invalid number of bytes to compare"));
523           break;
524
525         case_GETOPT_HELP_CHAR;
526
527         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
528
529         default:
530           usage (EXIT_FAILURE);
531         }
532     }
533
534   if (obsolete_skip_fields && 200112 <= posix2_version ())
535     {
536       error (0, 0, _("`-%lu' option is obsolete; use `-f %lu'"),
537              (unsigned long) skip_fields, (unsigned long) skip_fields);
538       usage (EXIT_FAILURE);
539     }
540
541   if (countmode == count_occurrences && output_later_repeated)
542     {
543       error (0, 0,
544            _("printing all duplicated lines and repeat counts is meaningless"));
545       usage (EXIT_FAILURE);
546     }
547
548   check_file (file[0], file[1]);
549
550   exit (EXIT_SUCCESS);
551 }