b08f190f866f7e70d71ee3427fbebdb590257099
[platform/upstream/coreutils.git] / src / uniq.c
1 /* uniq -- remove duplicate lines from a sorted file
2    Copyright (C) 86, 91, 1995-1998, 1999 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 "linebuffer.h"
28 #include "long-options.h"
29 #include "error.h"
30 #include "xstrtol.h"
31 #include "memcasecmp.h"
32
33 /* The official name of this program (e.g., no `g' prefix).  */
34 #define PROGRAM_NAME "uniq"
35
36 /* Undefine, to avoid warning about redefinition on some systems.  */
37 #undef min
38 #define min(x, y) ((x) < (y) ? (x) : (y))
39
40 /* The name this program was run with. */
41 char *program_name;
42
43 /* Number of fields to skip on each line when doing comparisons. */
44 static int skip_fields;
45
46 /* Number of chars to skip after skipping any fields. */
47 static int skip_chars;
48
49 /* Number of chars to compare; if 0, compare the whole lines. */
50 static int check_chars;
51
52 enum countmode
53 {
54   count_occurrences,            /* -c Print count before output lines. */
55   count_none                    /* Default.  Do not print counts. */
56 };
57
58 /* Whether and how to precede the output lines with a count of the number of
59    times they occurred in the input. */
60 static enum countmode countmode;
61
62 enum output_mode
63 {
64   output_repeated,              /* -d Only lines that are repeated. */
65   output_all_repeated,          /* -D All lines that are repeated. */
66   output_unique,                /* -u Only lines that are not repeated. */
67   output_all                    /* Default.  Print first copy of each line. */
68 };
69
70 /* Which lines to output. */
71 static enum output_mode mode;
72
73 /* If nonzero, ignore case when comparing.  */
74 static int ignore_case;
75
76 static struct option const longopts[] =
77 {
78   {"count", no_argument, NULL, 'c'},
79   {"repeated", no_argument, NULL, 'd'},
80   {"all-repeated", no_argument, NULL, 'D'},
81   {"ignore-case", no_argument, NULL, 'i'},
82   {"unique", no_argument, NULL, 'u'},
83   {"skip-fields", required_argument, NULL, 'f'},
84   {"skip-chars", required_argument, NULL, 's'},
85   {"check-chars", required_argument, NULL, 'w'},
86   {NULL, 0, NULL, 0}
87 };
88
89 void
90 usage (int status)
91 {
92   if (status != 0)
93     fprintf (stderr, _("Try `%s --help' for more information.\n"),
94              program_name);
95   else
96     {
97       printf (_("\
98 Usage: %s [OPTION]... [INPUT [OUTPUT]]\n\
99 "),
100               program_name);
101       printf (_("\
102 Discard all but one of successive identical lines from INPUT (or\n\
103 standard input), writing to OUTPUT (or standard output).\n\
104 \n\
105   -c, --count           prefix lines by the number of occurrences\n\
106   -d, --repeated        only print duplicate lines\n\
107   -D, --all-repeated    print all duplicate lines\n\
108   -f, --skip-fields=N   avoid comparing the first N fields\n\
109   -i, --ignore-case     ignore differences in case when comparing\n\
110   -s, --skip-chars=N    avoid comparing the first N characters\n\
111   -u, --unique          only print unique lines\n\
112   -w, --check-chars=N   compare no more than N characters in lines\n\
113   -N                    same as -f N\n\
114   +N                    same as -s N\n\
115       --help            display this help and exit\n\
116       --version         output version information and exit\n\
117 \n\
118 A field is a run of whitespace, then non-whitespace characters.\n\
119 Fields are skipped before chars.\n\
120 "));
121       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
122     }
123   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
124 }
125
126 /* Given a linebuffer LINE,
127    return a pointer to the beginning of the line's field to be compared. */
128
129 static char *
130 find_field (const struct linebuffer *line)
131 {
132   register int count;
133   register char *lp = line->buffer;
134   register int size = line->length;
135   register int i = 0;
136
137   for (count = 0; count < skip_fields && i < size; count++)
138     {
139       while (i < size && ISBLANK (lp[i]))
140         i++;
141       while (i < size && !ISBLANK (lp[i]))
142         i++;
143     }
144
145   for (count = 0; count < skip_chars && i < size; count++)
146     i++;
147
148   return lp + i;
149 }
150
151 /* Return zero if two strings OLD and NEW match, nonzero if not.
152    OLD and NEW point not to the beginnings of the lines
153    but rather to the beginnings of the fields to compare.
154    OLDLEN and NEWLEN are their lengths. */
155
156 static int
157 different (const char *old, const char *new, int oldlen, int newlen)
158 {
159   register int order;
160
161   if (check_chars)
162     {
163       if (oldlen > check_chars)
164         oldlen = check_chars;
165       if (newlen > check_chars)
166         newlen = check_chars;
167     }
168
169   /* Use an if-statement here rather than a function variable to
170      avoid portability hassles of getting a non-conflicting declaration
171      of memcmp.  */
172   if (ignore_case)
173     order = memcasecmp (old, new, min (oldlen, newlen));
174   else
175     order = memcmp (old, new, min (oldlen, newlen));
176
177   if (order == 0)
178     return oldlen - newlen;
179   return order;
180 }
181
182 /* Output the line in linebuffer LINE to stream STREAM
183    provided that the switches say it should be output.
184    If requested, print the number of times it occurred, as well;
185    LINECOUNT + 1 is the number of times that the line occurred. */
186
187 static void
188 writeline (const struct linebuffer *line, FILE *stream, int linecount)
189 {
190   if ((mode == output_unique && linecount != 0)
191       || (mode == output_repeated && linecount == 0)
192       || (mode == output_all_repeated && linecount == 0))
193     return;
194
195   if (countmode == count_occurrences)
196     fprintf (stream, "%7d\t", linecount + 1);
197
198   fwrite (line->buffer, sizeof (char), line->length, stream);
199   putc ('\n', stream);
200 }
201
202 /* Process input file INFILE with output to OUTFILE.
203    If either is "-", use the standard I/O stream for it instead. */
204
205 static void
206 check_file (const char *infile, const char *outfile)
207 {
208   FILE *istream;
209   FILE *ostream;
210   struct linebuffer lb1, lb2;
211   struct linebuffer *thisline, *prevline, *exch;
212   char *prevfield, *thisfield;
213   int prevlen, thislen;
214   int match_count = 0;
215
216   if (STREQ (infile, "-"))
217     istream = stdin;
218   else
219     istream = fopen (infile, "r");
220   if (istream == NULL)
221     error (EXIT_FAILURE, errno, "%s", infile);
222
223   if (STREQ (outfile, "-"))
224     ostream = stdout;
225   else
226     ostream = fopen (outfile, "w");
227   if (ostream == NULL)
228     error (EXIT_FAILURE, errno, "%s", outfile);
229
230   thisline = &lb1;
231   prevline = &lb2;
232
233   initbuffer (thisline);
234   initbuffer (prevline);
235
236   if (readline (prevline, istream) == 0)
237     goto closefiles;
238   prevfield = find_field (prevline);
239   prevlen = prevline->length - (prevfield - prevline->buffer);
240
241   while (!feof (istream))
242     {
243       int match;
244       if (readline (thisline, istream) == 0)
245         break;
246       thisfield = find_field (thisline);
247       thislen = thisline->length - (thisfield - thisline->buffer);
248       match = !different (thisfield, prevfield, thislen, prevlen);
249
250       if (match)
251         ++match_count;
252
253       if (!match || mode == output_all_repeated)
254         {
255           writeline (prevline, ostream, match_count);
256           exch = prevline;
257           prevline = thisline;
258           thisline = exch;
259           prevfield = thisfield;
260           prevlen = thislen;
261           if (!match)
262             match_count = 0;
263         }
264     }
265
266   writeline (prevline, ostream, match_count);
267
268  closefiles:
269   if (ferror (istream) || fclose (istream) == EOF)
270     error (EXIT_FAILURE, errno, _("error reading %s"), infile);
271
272   if (ferror (ostream) || fclose (ostream) == EOF)
273     error (EXIT_FAILURE, errno, _("error writing %s"), outfile);
274
275   free (lb1.buffer);
276   free (lb2.buffer);
277 }
278
279 int
280 main (int argc, char **argv)
281 {
282   int optc;
283   char *infile = "-", *outfile = "-";
284
285   program_name = argv[0];
286   setlocale (LC_ALL, "");
287   bindtextdomain (PACKAGE, LOCALEDIR);
288   textdomain (PACKAGE);
289
290   parse_long_options (argc, argv, "uniq", GNU_PACKAGE, VERSION,
291                       "Richard Stallman and David MacKenzie", usage);
292
293   skip_chars = 0;
294   skip_fields = 0;
295   check_chars = 0;
296   mode = output_all;
297   countmode = count_none;
298
299   while ((optc = getopt_long (argc, argv, "0123456789cdDf:is:uw:", longopts,
300                               NULL)) != -1)
301     {
302       switch (optc)
303         {
304         case 0:
305           break;
306
307         case '0':
308         case '1':
309         case '2':
310         case '3':
311         case '4':
312         case '5':
313         case '6':
314         case '7':
315         case '8':
316         case '9':
317           skip_fields = skip_fields * 10 + optc - '0';
318           break;
319
320         case 'c':
321           countmode = count_occurrences;
322           break;
323
324         case 'd':
325           mode = output_repeated;
326           break;
327
328         case 'D':
329           mode = output_all_repeated;
330           break;
331
332         case 'f':               /* Like '-#'. */
333           {
334             long int tmp_long;
335             if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
336                 || tmp_long <= 0 || tmp_long > INT_MAX)
337               error (EXIT_FAILURE, 0,
338                      _("invalid number of fields to skip: `%s'"),
339                      optarg);
340             skip_fields = (int) tmp_long;
341           }
342           break;
343
344         case 'i':
345           ignore_case = 1;
346           break;
347
348         case 's':               /* Like '+#'. */
349           {
350             long int tmp_long;
351             if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
352                 || tmp_long <= 0 || tmp_long > INT_MAX)
353               error (EXIT_FAILURE, 0,
354                      _("invalid number of bytes to skip: `%s'"),
355                      optarg);
356             skip_chars = (int) tmp_long;
357           }
358           break;
359
360         case 'u':
361           mode = output_unique;
362           break;
363
364         case 'w':
365           {
366             long int tmp_long;
367             if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
368                 || tmp_long <= 0 || tmp_long > INT_MAX)
369               error (EXIT_FAILURE, 0,
370                      _("invalid number of bytes to compare: `%s'"),
371                      optarg);
372             check_chars = (int) tmp_long;
373           }
374           break;
375
376         default:
377           usage (1);
378         }
379     }
380
381   if (optind >= 2 && !STREQ (argv[optind - 1], "--"))
382     {
383       /* Interpret non-option arguments with leading `+' only
384          if we haven't seen `--'.  */
385       while (optind < argc && argv[optind][0] == '+')
386         {
387           char *opt_str = argv[optind++];
388           long int tmp_long;
389           if (xstrtol (opt_str, NULL, 10, &tmp_long, "") != LONGINT_OK
390               || tmp_long <= 0 || tmp_long > INT_MAX)
391             error (EXIT_FAILURE, 0,
392                    _("invalid number of bytes to compare: `%s'"),
393                    opt_str);
394           skip_chars = (int) tmp_long;
395         }
396     }
397
398   if (optind < argc)
399     infile = argv[optind++];
400
401   if (optind < argc)
402     outfile = argv[optind++];
403
404   if (optind < argc)
405     {
406       error (0, 0, _("too many arguments"));
407       usage (1);
408     }
409
410   if (countmode == count_occurrences && mode == output_all_repeated)
411     {
412       error (0, 0,
413            _("printing all duplicated lines and repeat counts is meaningless"));
414       usage (1);
415     }
416
417   check_file (infile, outfile);
418
419   exit (EXIT_SUCCESS);
420 }