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