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