64a5da3d3e3fded13bc58d39951f8c4209938c2c
[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., 675 Mass Ave, Cambridge, MA 02139, 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 #include "system.h"
29 #include "linebuffer.h"
30 #include "version.h"
31 #include "error.h"
32
33 #define min(x, y) ((x) < (y) ? (x) : (y))
34
35 static char *find_field ();
36 static int different ();
37 static void check_file ();
38 static void usage ();
39 static void writeline ();
40
41 /* The name this program was run with. */
42 char *program_name;
43
44 /* Number of fields to skip on each line when doing comparisons. */
45 static int skip_fields;
46
47 /* Number of chars to skip after skipping any fields. */
48 static int skip_chars;
49
50 /* Number of chars to compare; if 0, compare the whole lines. */
51 static int check_chars;
52
53 enum countmode
54 {
55   count_occurrences,            /* -c Print count before output lines. */
56   count_none                    /* Default.  Do not print counts. */
57 };
58
59 /* Whether and how to precede the output lines with a count of the number of
60    times they occurred in the input. */
61 static enum countmode countmode;
62
63 enum output_mode
64 {
65   output_repeated,              /* -d Only 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 non-zero, display usage information and exit.  */
74 static int show_help;
75
76 /* If non-zero, print the version on standard output then exit.  */
77 static int show_version;
78
79 static struct option const longopts[] =
80 {
81   {"count", no_argument, NULL, 'c'},
82   {"repeated", no_argument, NULL, 'd'},
83   {"unique", no_argument, NULL, 'u'},
84   {"skip-fields", required_argument, NULL, 'f'},
85   {"skip-chars", required_argument, NULL, 's'},
86   {"check-chars", required_argument, NULL, 'w'},
87   {"help", no_argument, &show_help, 1},
88   {"version", no_argument, &show_version, 1},
89   {NULL, 0, NULL, 0}
90 };
91 \f
92 void
93 main (argc, argv)
94      int argc;
95      char *argv[];
96 {
97   int optc;
98   char *infile = "-", *outfile = "-";
99
100   program_name = argv[0];
101   skip_chars = 0;
102   skip_fields = 0;
103   check_chars = 0;
104   mode = output_all;
105   countmode = count_none;
106
107   while ((optc = getopt_long (argc, argv, "0123456789cdf:s:uw:", longopts,
108                               (int *) 0)) != EOF)
109     {
110       switch (optc)
111         {
112         case 0:
113           break;
114
115         case '0':
116         case '1':
117         case '2':
118         case '3':
119         case '4':
120         case '5':
121         case '6':
122         case '7':
123         case '8':
124         case '9':
125           skip_fields = skip_fields * 10 + optc - '0';
126           break;
127
128         case 'c':
129           countmode = count_occurrences;
130           break;
131
132         case 'd':
133           mode = output_repeated;
134           break;
135
136         case 'f':               /* Like '-#'. */
137           skip_fields = atoi (optarg);
138           break;
139
140         case 's':               /* Like '+#'. */
141           skip_chars = atoi (optarg);
142           break;
143
144         case 'u':
145           mode = output_unique;
146           break;
147
148         case 'w':
149           check_chars = atoi (optarg);
150           break;
151
152         default:
153           usage (1);
154         }
155     }
156
157   if (show_version)
158     {
159       printf ("uniq - %s\n", version_string);
160       exit (0);
161     }
162
163   if (show_help)
164     usage (0);
165
166   if (optind >= 2 && strcmp (argv[optind - 1], "--") != 0)
167     {
168       /* Interpret non-option arguments with leading `+' only
169          if we haven't seen `--'.  */
170       while (optind < argc && argv[optind][0] == '+')
171         skip_chars = atoi (argv[optind++]);
172     }
173
174   if (optind < argc)
175     infile = argv[optind++];
176
177   if (optind < argc)
178     outfile = argv[optind++];
179
180   if (optind < argc)
181     usage (1);                  /* Extra arguments. */
182
183   check_file (infile, outfile);
184
185   exit (0);
186 }
187 \f
188 /* Process input file INFILE with output to OUTFILE.
189    If either is "-", use the standard I/O stream for it instead. */
190
191 static void
192 check_file (infile, outfile)
193      char *infile, *outfile;
194 {
195   FILE *istream;
196   FILE *ostream;
197   struct linebuffer lb1, lb2;
198   struct linebuffer *thisline, *prevline, *exch;
199   char *prevfield, *thisfield;
200   int prevlen, thislen;
201   int match_count = 0;
202
203   if (!strcmp (infile, "-"))
204     istream = stdin;
205   else
206     istream = fopen (infile, "r");
207   if (istream == NULL)
208     error (1, errno, "%s", infile);
209
210   if (!strcmp (outfile, "-"))
211     ostream = stdout;
212   else
213     ostream = fopen (outfile, "w");
214   if (ostream == NULL)
215     error (1, errno, "%s", outfile);
216
217   thisline = &lb1;
218   prevline = &lb2;
219
220   initbuffer (thisline);
221   initbuffer (prevline);
222
223   if (readline (prevline, istream) == 0)
224     goto closefiles;
225   prevfield = find_field (prevline);
226   prevlen = prevline->length - (prevfield - prevline->buffer);
227
228   while (!feof (istream))
229     {
230       if (readline (thisline, istream) == 0)
231         break;
232       thisfield = find_field (thisline);
233       thislen = thisline->length - (thisfield - thisline->buffer);
234       if (!different (thisfield, prevfield, thislen, prevlen))
235         match_count++;
236       else
237         {
238           writeline (prevline, ostream, match_count);
239           match_count = 0;
240
241           exch = prevline;
242           prevline = thisline;
243           thisline = exch;
244           prevfield = thisfield;
245           prevlen = thislen;
246         }
247     }
248
249   writeline (prevline, ostream, match_count);
250
251  closefiles:
252   if (ferror (istream) || fclose (istream) == EOF)
253     error (1, errno, "error reading %s", infile);
254
255   if (ferror (ostream) || fclose (ostream) == EOF)
256     error (1, errno, "error writing %s", outfile);
257
258   free (lb1.buffer);
259   free (lb2.buffer);
260 }
261 \f
262 /* Given a linebuffer LINE,
263    return a pointer to the beginning of the line's field to be compared. */
264
265 static char *
266 find_field (line)
267      struct linebuffer *line;
268 {
269   register int count;
270   register char *lp = line->buffer;
271   register int size = line->length;
272   register int i = 0;
273
274   for (count = 0; count < skip_fields && i < size; count++)
275     {
276       while (i < size && ISBLANK (lp[i]))
277         i++;
278       while (i < size && !ISBLANK (lp[i]))
279         i++;
280     }
281
282   for (count = 0; count < skip_chars && i < size; count++)
283     i++;
284
285   return lp + i;
286 }
287
288 /* Return zero if two strings OLD and NEW match, nonzero if not.
289    OLD and NEW point not to the beginnings of the lines
290    but rather to the beginnings of the fields to compare.
291    OLDLEN and NEWLEN are their lengths. */
292
293 static int
294 different (old, new, oldlen, newlen)
295      char *old;
296      char *new;
297      int oldlen;
298      int newlen;
299 {
300   register int order;
301
302   if (check_chars)
303     {
304       if (oldlen > check_chars)
305         oldlen = check_chars;
306       if (newlen > check_chars)
307         newlen = check_chars;
308     }
309   order = memcmp (old, new, min (oldlen, newlen));
310   if (order == 0)
311     return oldlen - newlen;
312   return order;
313 }
314 \f
315 /* Output the line in linebuffer LINE to stream STREAM
316    provided that the switches say it should be output.
317    If requested, print the number of times it occurred, as well;
318    LINECOUNT + 1 is the number of times that the line occurred. */
319
320 static void
321 writeline (line, stream, linecount)
322      struct linebuffer *line;
323      FILE *stream;
324      int linecount;
325 {
326   if ((mode == output_unique && linecount != 0)
327       || (mode == output_repeated && linecount == 0))
328     return;
329
330   if (countmode == count_occurrences)
331     fprintf (stream, "%7d\t", linecount + 1);
332
333   fwrite (line->buffer, sizeof (char), line->length, stream);
334   putc ('\n', stream);
335 }
336 \f
337 static void
338 usage (status)
339      int status;
340 {
341   if (status != 0)
342     fprintf (stderr, "Try `%s --help' for more information.\n",
343              program_name);
344   else
345     {
346       printf ("\
347 Usage: %s [OPTION]... [INPUT [OUTPUT]]\n\
348 ",
349               program_name);
350       printf ("\
351 \n\
352   -c, --count           prefix lines by the number of occurrences\n\
353   -d, --repeated        only print duplicate lines\n\
354   -f, --skip-fields=N   avoid comparing the N first fields\n\
355   -s, --skip-chars=N    avoid comparing the N first characters\n\
356   -u, --unique          only print unique lines\n\
357   -w, --check-chars=N   compare no more then N characters in lines\n\
358   -N                    same as -f N\n\
359   +N                    same as -s N\n\
360       --help            display this help and exit\n\
361       --version         output version information and exit\n\
362 \n\
363 A field is a run of whitespace, than non whitespace characters.\n\
364 Fields are skipped before chars.  If OUTPUT not specified, writes to\n\
365 standard output.  If INPUT not specified, reads from standard input.\n\
366 ");
367     }
368   exit (status);
369 }