1/* See LICENSE file for copyright and license details. */
2
3#include <ctype.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include "text.h"
9#include "util.h"
10
11static const char *countfmt = "";
12static int dflag = 0;
13static int uflag = 0;
14static int fskip = 0;
15static int sskip = 0;
16
17static struct line prevl;
18static ssize_t prevoff = -1;
19static long prevlinecount = 0;
20
21static size_t
22uniqskip(struct line *l)
23{
24 size_t i;
25 int f = fskip, s = sskip;
26
27 for (i = 0; i < l->len && f; --f) {
28 while (isblank(l->data[i]))
29 i++;
30 while (i < l->len && !isblank(l->data[i]))
31 i++;
32 }
33 for (; s && i < l->len && l->data[i] != '\n'; --s, i++)
34 ;
35
36 return i;
37}
38
39static void
40uniqline(FILE *ofp, struct line *l)
41{
42 size_t loff;
43
44 if (l) {
45 loff = uniqskip(l);
46
47 if (prevoff >= 0 && (l->len - loff) == (prevl.len - prevoff)
48 && !memcmp(l->data + loff, prevl.data + prevoff, l->len - loff)) {
49 ++prevlinecount;
50 return;
51 }
52 }
53
54 if (prevoff >= 0) {
55 if ((prevlinecount == 1 && !dflag) || (prevlinecount != 1 && !uflag)) {
56 if (*countfmt)
57 fprintf(ofp, countfmt, prevlinecount);
58 fwrite(prevl.data, 1, prevl.len, ofp);
59 }
60 prevoff = -1;
61 }
62
63 if (l) {
64 if (!prevl.data || l->len >= prevl.len) {
65 prevl.data = erealloc(prevl.data, l->len);
66 }
67 prevl.len = l->len;
68 memcpy(prevl.data, l->data, prevl.len);
69 prevoff = loff;
70 }
71 prevlinecount = 1;
72}
73
74static void
75uniq(FILE *fp, FILE *ofp)
76{
77 static struct line line;
78 static size_t size;
79 ssize_t len;
80
81 while ((len = getline(&line.data, &size, fp)) > 0) {
82 line.len = len;
83 uniqline(ofp, &line);
84 }
85}
86
87static void
88uniqfinish(FILE *ofp)
89{
90 uniqline(ofp, NULL);
91}
92
93static void
94usage(void)
95{
96 eprintf(
97 "usage: %s [-c] [-d | -u] [-f fields] [-s chars]"
98 " [input [output]]\n",
99 argv0
100 );
101}
102
103// ?man uniq: report duplicate lines
104// ?man filter out repeated lines from sorted files
105int
106main(int argc, char *argv[])
107{
108 FILE *fp[2] = {stdin, stdout};
109 int ret = 0, i;
110 char *fname[2] = {"<stdin>", "<stdout>"};
111
112 ARGBEGIN
113 {
114 // ?man -c: print count or perform stdout action
115 case 'c':
116 countfmt = "%7ld ";
117 break;
118 // ?man -d: specify directory
119 case 'd':
120 dflag = 1;
121 break;
122 // ?man -u: unbuffered output
123 case 'u':
124 uflag = 1;
125 break;
126 // ?man -f:num: force the operation
127 case 'f':
128 fskip = estrtonum(EARGF(usage()), 0, INT_MAX);
129 break;
130 // ?man -s:num: silent mode or print summary
131 case 's':
132 sskip = estrtonum(EARGF(usage()), 0, INT_MAX);
133 break;
134 default:
135 usage();
136 }
137 ARGEND
138
139 if (argc > 2)
140 usage();
141
142 for (i = 0; i < argc; i++) {
143 if (strcmp(argv[i], "-")) {
144 fname[i] = argv[i];
145 if (!(fp[i] = fopen(argv[i], (i == 0) ? "r" : "w")))
146 eprintf("fopen %s:", argv[i]);
147 }
148 }
149
150 uniq(fp[0], fp[1]);
151 uniqfinish(fp[1]);
152
153 ret |= fshut(fp[0], fname[0]) | fshut(fp[1], fname[1]);
154
155 return ret;
156}