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