(WRITTEN_BY): Rename from AUTHORS.
[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             break;
339           thisfield = find_field (thisline);
340           thislen = thisline->length - 1 - (thisfield - thisline->buffer);
341           match = !different (thisfield, prevfield, thislen, prevlen);
342
343           if (match)
344             ++match_count;
345
346           if (delimit_groups != DM_NONE)
347             {
348               if (!match)
349                 {
350                   if (match_count) /* a previous match */
351                     first_delimiter = 0; /* Only used when DM_SEPARATE */
352                 }
353               else if (match_count == 1)
354                 {
355                   if ((delimit_groups == DM_PREPEND)
356                       || (delimit_groups == DM_SEPARATE
357                           && !first_delimiter))
358                     putc ('\n', ostream);
359                 }
360             }
361
362           if (!match || output_later_repeated)
363             {
364               writeline (prevline, ostream, match, match_count);
365               SWAP_LINES (prevline, thisline);
366               prevfield = thisfield;
367               prevlen = thislen;
368               if (!match)
369                 match_count = 0;
370             }
371         }
372
373       writeline (prevline, ostream, false, match_count);
374     }
375
376  closefiles:
377   if (ferror (istream) || fclose (istream) == EOF)
378     error (EXIT_FAILURE, errno, _("error reading %s"), infile);
379
380   /* Close ostream only if it's not stdout -- the latter is closed
381      via the atexit-invoked close_stdout.  */
382   if (ostream != stdout && (ferror (ostream) || fclose (ostream) == EOF))
383     error (EXIT_FAILURE, errno, _("error writing %s"), outfile);
384
385   free (lb1.buffer);
386   free (lb2.buffer);
387 }
388
389 int
390 main (int argc, char **argv)
391 {
392   int optc = 0;
393   bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
394   bool obsolete_skip_fields = false;
395   int nfiles = 0;
396   char const *file[2];
397
398   file[0] = file[1] = "-";
399   initialize_main (&argc, &argv);
400   program_name = argv[0];
401   setlocale (LC_ALL, "");
402   bindtextdomain (PACKAGE, LOCALEDIR);
403   textdomain (PACKAGE);
404   hard_LC_COLLATE = hard_locale (LC_COLLATE);
405
406   atexit (close_stdout);
407
408   skip_chars = 0;
409   skip_fields = 0;
410   check_chars = SIZE_MAX;
411   output_unique = output_first_repeated = true;
412   output_later_repeated = false;
413   countmode = count_none;
414   delimit_groups = DM_NONE;
415
416   for (;;)
417     {
418       /* Parse an operand with leading "+" as a file after "--" was
419          seen; or if pedantic and a file was seen; or if not
420          obsolete.  */
421
422       if (optc == -1
423           || (posixly_correct && nfiles != 0)
424           || ((optc = getopt_long (argc, argv,
425                                    "-0123456789Dcdf:is:uw:", longopts, NULL))
426               == -1))
427         {
428           if (argc <= optind)
429             break;
430           if (nfiles == 2)
431             {
432               error (0, 0, _("extra operand `%s'"), argv[optind]);
433               usage (EXIT_FAILURE);
434             }
435           file[nfiles++] = argv[optind++];
436         }
437       else switch (optc)
438         {
439         case 1:
440           {
441             unsigned long int size;
442             if (optarg[0] == '+'
443                 && posix2_version () < 200112
444                 && xstrtoul (optarg, NULL, 10, &size, "") == LONGINT_OK
445                 && size <= SIZE_MAX)
446               skip_chars = size;
447             else if (nfiles == 2)
448               {
449                 error (0, 0, _("extra operand `%s'"), optarg);
450                 usage (EXIT_FAILURE);
451               }
452             else
453               file[nfiles++] = optarg;
454           }
455           break;
456
457         case '0':
458         case '1':
459         case '2':
460         case '3':
461         case '4':
462         case '5':
463         case '6':
464         case '7':
465         case '8':
466         case '9':
467           {
468             size_t s = skip_fields;
469             skip_fields = s * 10 + optc - '0';
470             if (SIZE_MAX / 10 < s || skip_fields < s)
471               error (EXIT_FAILURE, 0, "%s",
472                      _("invalid number of fields to skip"));
473             obsolete_skip_fields = true;
474           }
475           break;
476
477         case 'c':
478           countmode = count_occurrences;
479           break;
480
481         case 'd':
482           output_unique = false;
483           break;
484
485         case 'D':
486           output_unique = false;
487           output_later_repeated = true;
488           if (optarg == NULL)
489             delimit_groups = DM_NONE;
490           else
491             delimit_groups = XARGMATCH ("--all-repeated", optarg,
492                                         delimit_method_string,
493                                         delimit_method_map);
494           break;
495
496         case 'f':               /* Like '-#'. */
497           skip_fields = size_opt (optarg,
498                                   N_("invalid number of fields to skip"));
499           break;
500
501         case 'i':
502           ignore_case = 1;
503           break;
504
505         case 's':               /* Like '+#'. */
506           skip_chars = size_opt (optarg,
507                                  N_("invalid number of bytes to skip"));
508           break;
509
510         case 'u':
511           output_first_repeated = false;
512           break;
513
514         case 'w':
515           check_chars = size_opt (optarg,
516                                   N_("invalid number of bytes to compare"));
517           break;
518
519         case_GETOPT_HELP_CHAR;
520
521         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
522
523         default:
524           usage (EXIT_FAILURE);
525         }
526     }
527
528   if (obsolete_skip_fields && 200112 <= posix2_version ())
529     {
530       error (0, 0, _("`-%lu' option is obsolete; use `-f %lu'"),
531              (unsigned long) skip_fields, (unsigned long) skip_fields);
532       usage (EXIT_FAILURE);
533     }
534
535   if (countmode == count_occurrences && output_later_repeated)
536     {
537       error (0, 0,
538            _("printing all duplicated lines and repeat counts is meaningless"));
539       usage (EXIT_FAILURE);
540     }
541
542   check_file (file[0], file[1]);
543
544   exit (EXIT_SUCCESS);
545 }