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