1da999d983bbf919e080d36d318a1a31f027b7d2
[dragonfly.git] / usr.bin / cut / cut.c
1 /*
2  * Copyright (c) 1989, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 4. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * @(#) Copyright (c) 1989, 1993 The Regents of the University of California.  All rights reserved.
33  * @(#)cut.c    8.3 (Berkeley) 5/4/95
34  * $FreeBSD: src/usr.bin/cut/cut.c,v 1.9.2.3 2001/07/30 09:59:16 dd Exp $
35  */
36
37 #include <ctype.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <limits.h>
41 #include <locale.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <wchar.h>
47
48 static int      bflag;
49 static int      cflag;
50 static wchar_t  dchar;
51 static char     dcharmb[MB_LEN_MAX + 1];
52 static int      dflag;
53 static int      fflag;
54 static int      nflag;
55 static int      sflag;
56
57 static size_t   autostart, autostop, maxval;
58 static char *   positions;
59
60 static int      b_cut(FILE *, const char *);
61 static int      b_n_cut(FILE *, const char *);
62 static int      c_cut(FILE *, const char *);
63 static int      f_cut(FILE *, const char *);
64 static void     get_list(char *);
65 static void     needpos(size_t);
66 static void     usage(void);
67
68 int
69 main(int argc, char *argv[])
70 {
71         FILE *fp;
72         int (*fcn)(FILE *, const char *);
73         int ch, rval;
74         size_t n;
75
76         setlocale (LC_ALL, "");
77
78         fcn = NULL;
79         dchar = '\t';                   /* default delimiter is \t */
80         strcpy(dcharmb, "\t");
81
82         while ((ch = getopt(argc, argv, "b:c:d:f:sn")) != -1)
83                 switch(ch) {
84                 case 'b':
85                         get_list(optarg);
86                         bflag = 1;
87                         break;
88                 case 'c':
89                         get_list(optarg);
90                         cflag = 1;
91                         break;
92                 case 'd':
93                         n = mbrtowc(&dchar, optarg, MB_LEN_MAX, NULL);
94                         if (dchar == '\0' || n != strlen(optarg))
95                                 errx(1, "bad delimiter");
96                         strcpy(dcharmb, optarg);
97                         dflag = 1;
98                         break;
99                 case 'f':
100                         get_list(optarg);
101                         fflag = 1;
102                         break;
103                 case 's':
104                         sflag = 1;
105                         break;
106                 case 'n':
107                         nflag = 1;
108                         break;
109                 case '?':
110                 default:
111                         usage();
112                 }
113         argc -= optind;
114         argv += optind;
115
116         if (fflag) {
117                 if (bflag || cflag || nflag)
118                         usage();
119         } else if (!(bflag || cflag) || dflag || sflag)
120                         usage();
121         else if (!bflag && nflag)
122                 usage();
123
124         if (fflag)
125                 fcn = f_cut;
126         else if (cflag)
127                 fcn = MB_CUR_MAX > 1 ? c_cut : b_cut;
128         else if (bflag)
129                 fcn = nflag && MB_CUR_MAX > 1 ? b_n_cut : b_cut;
130
131         rval = 0;
132         if (*argv)
133                 for (; *argv; ++argv) {
134                         if (strcmp(*argv, "-") == 0)
135                                 rval |= fcn(stdin, "stdin");
136                         else {
137                                 if (!(fp = fopen(*argv, "r"))) {
138                                         warn("%s", *argv);
139                                         rval = 1;
140                                         continue;
141                                 }
142                         fcn(fp, *argv);
143                         (void)fclose(fp);
144                 }
145                 }
146         else
147                 rval = fcn(stdin, "stdin");
148         exit(rval);
149 }
150
151 static void
152 get_list(char *list)
153 {
154         size_t setautostart, start, stop;
155         char *pos;
156         char *p;
157
158         /*
159          * set a byte in the positions array to indicate if a field or
160          * column is to be selected; use +1, it's 1-based, not 0-based.
161          * Numbers and number ranges may be overlapping, repeated, and in
162          * any order. We handle "-3-5" although there's no real reason to.
163          */
164         for (; (p = strsep(&list, ", \t")) != NULL;) {
165                 setautostart = start = stop = 0;
166                 if (*p == '-') {
167                         ++p;
168                         setautostart = 1;
169                 }
170                 if (isdigit((unsigned char)*p)) {
171                         start = stop = strtol(p, &p, 10);
172                         if (setautostart && start > autostart)
173                                 autostart = start;
174                 }
175                 if (*p == '-') {
176                         if (isdigit((unsigned char)p[1]))
177                                 stop = strtol(p + 1, &p, 10);
178                         if (*p == '-') {
179                                 ++p;
180                                 if (!autostop || autostop > stop)
181                                         autostop = stop;
182                         }
183                 }
184                 if (*p)
185                         errx(1, "[-bcf] list: illegal list value");
186                 if (!stop || !start)
187                         errx(1, "[-bcf] list: values may not include zero");
188                 if (maxval < stop) {
189                         maxval = stop;
190                         needpos(maxval + 1);
191                 }
192                 for (pos = positions + start; start++ <= stop; *pos++ = 1);
193         }
194
195         /* overlapping ranges */
196         if (autostop && maxval > autostop) {
197                 maxval = autostop;
198                 needpos(maxval + 1);
199         }
200
201         /* set autostart */
202         if (autostart)
203                 memset(positions + 1, '1', autostart);
204 }
205
206 static void
207 needpos(size_t n)
208 {
209         static size_t npos;
210         size_t oldnpos;
211
212         /* Grow the positions array to at least the specified size. */
213         if (n > npos) {
214                 oldnpos = npos;
215                 if (npos == 0)
216                         npos = n;
217                 while (n > npos)
218                         npos *= 2;
219                 if ((positions = realloc(positions, npos)) == NULL)
220                         err(1, "realloc");
221                 memset((char *)positions + oldnpos, 0, npos - oldnpos);
222         }
223 }
224
225 static int
226 b_cut(FILE *fp, const char *fname __unused)
227 {
228         int ch, col;
229         char *pos;
230
231         ch = 0;
232         for (;;) {
233                 pos = positions + 1;
234                 for (col = maxval; col; --col) {
235                         if ((ch = getc(fp)) == EOF)
236                                 return (0);
237                         if (ch == '\n')
238                                 break;
239                         if (*pos++)
240                                 (void)putchar(ch);
241                 }
242                 if (ch != '\n') {
243                         if (autostop)
244                                 while ((ch = getc(fp)) != EOF && ch != '\n')
245                                         (void)putchar(ch);
246                         else
247                                 while ((ch = getc(fp)) != EOF && ch != '\n');
248                 }
249                 (void)putchar('\n');
250         }
251         return (0);
252 }
253
254 /*
255  * Cut based on byte positions, taking care not to split multibyte characters.
256  * Although this function also handles the case where -n is not specified,
257  * b_cut() ought to be much faster.
258  */
259 static int
260 b_n_cut(FILE *fp, const char *fname)
261 {
262         size_t col, i, lbuflen;
263         char *lbuf;
264         int canwrite, clen, warned;
265         mbstate_t mbs;
266
267         memset(&mbs, 0, sizeof(mbs));
268         warned = 0;
269         while ((lbuf = fgetln(fp, &lbuflen)) != NULL) {
270                 for (col = 0; lbuflen > 0; col += clen) {
271                         if ((clen = mbrlen(lbuf, lbuflen, &mbs)) < 0) {
272                                 if (!warned) {
273                                         warn("%s", fname);
274                                         warned = 1;
275                                 }
276                                 memset(&mbs, 0, sizeof(mbs));
277                                 clen = 1;
278                         }
279                         if (clen == 0 || *lbuf == '\n')
280                                 break;
281                         if (col < maxval && !positions[1 + col]) {
282                                 /*
283                                  * Print the character if (1) after an initial
284                                  * segment of un-selected bytes, the rest of
285                                  * it is selected, and (2) the last byte is
286                                  * selected.
287                                  */
288                                 i = col;
289                                 while (i < col + clen && i < maxval &&
290                                     !positions[1 + i])
291                                         i++;
292                                 canwrite = i < col + clen;
293                                 for (; i < col + clen && i < maxval; i++)
294                                         canwrite &= positions[1 + i];
295                                 if (canwrite)
296                                         fwrite(lbuf, 1, clen, stdout);
297         } else {
298                                 /*
299                                  * Print the character if all of it has
300                                  * been selected.
301                                  */
302                                 canwrite = 1;
303                                 for (i = col; i < col + clen; i++)
304                                         if ((i >= maxval && !autostop) ||
305                                             (i < maxval && !positions[1 + i])) {
306                                                 canwrite = 0;
307                                                 break;
308                                         }
309                                 if (canwrite)
310                                         fwrite(lbuf, 1, clen, stdout);
311                         }
312                         lbuf += clen;
313                         lbuflen -= clen;
314                 }
315                 if (lbuflen > 0)
316                         putchar('\n');
317         }
318         return (warned);
319 }
320
321 static int
322 c_cut(FILE *fp, const char *fname)
323 {
324         wint_t ch;
325         int col;
326         char *pos;
327
328         ch = 0;
329         for (;;) {
330                 pos = positions + 1;
331                 for (col = maxval; col; --col) {
332                         if ((ch = getwc(fp)) == WEOF)
333                                 goto out;
334                         if (ch == '\n')
335                                 break;
336                         if (*pos++)
337                                 (void)putwchar(ch);
338         }
339                 if (ch != '\n') {
340                         if (autostop)
341                                 while ((ch = getwc(fp)) != WEOF && ch != '\n')
342                                         (void)putwchar(ch);
343                         else
344                                 while ((ch = getwc(fp)) != WEOF && ch != '\n');
345                 }
346                 (void)putwchar('\n');
347         }
348 out:
349         if (ferror(fp)) {
350                 warn("%s", fname);
351                 return (1);
352         }
353         return (0);
354 }
355
356 static int
357 f_cut(FILE *fp, const char *fname)
358 {
359         wchar_t ch;
360         int field, i, isdelim;
361         char *pos, *p;
362         wchar_t sep;
363         int output;
364         char *lbuf, *mlbuf;
365         size_t clen, lbuflen, reallen;
366
367         mlbuf = NULL;
368         for (sep = dchar; (lbuf = fgetln(fp, &lbuflen)) != NULL;) {
369                 reallen = lbuflen;
370                 /* Assert EOL has a newline. */
371                 if (*(lbuf + lbuflen - 1) != '\n') {
372                         /* Can't have > 1 line with no trailing newline. */
373                         mlbuf = malloc(lbuflen + 1);
374                         if (mlbuf == NULL)
375                                 err(1, "malloc");
376                         memcpy(mlbuf, lbuf, lbuflen);
377                         *(mlbuf + lbuflen) = '\n';
378                         lbuf = mlbuf;
379                         reallen++;
380                 }
381                 output = 0;
382                 for (isdelim = 0, p = lbuf;; p += clen) {
383                         clen = mbrtowc(&ch, p, lbuf + reallen - p, NULL);
384                         if (clen == (size_t)-1 || clen == (size_t)-2) {
385                                 warnc(EILSEQ, "%s", fname);
386                                 free(mlbuf);
387                                 return (1);
388                         }
389                         if (clen == 0)
390                                 clen = 1;
391                         /* this should work if newline is delimiter */
392                         if (ch == sep)
393                                 isdelim = 1;
394                         if (ch == '\n') {
395                                 if (!isdelim && !sflag)
396                                         (void)fwrite(lbuf, lbuflen, 1, stdout);
397                                 break;
398                         }
399                 }
400                 if (!isdelim)
401                         continue;
402
403                 pos = positions + 1;
404                 for (field = maxval, p = lbuf; field; --field, ++pos) {
405                         if (*pos && output++)
406                                 for (i = 0; dcharmb[i] != '\0'; i++)
407                                         putchar(dcharmb[i]);
408                         for (;;) {
409                                 clen = mbrtowc(&ch, p, lbuf + reallen - p,
410                                     NULL);
411                                 if (clen == (size_t)-1 || clen == (size_t)-2) {
412                                         warnc(EILSEQ, "%s", fname);
413                                         free(mlbuf);
414                                         return (1);
415                                 }
416                                 if (clen == 0)
417                                         clen = 1;
418                                 p += clen;
419                                 if (ch == '\n' || ch == sep)
420                                         break;
421                                 if (*pos)
422                                         for (i = 0; i < (int)clen; i++)
423                                                 putchar(p[i - clen]);
424                         }
425                         if (ch == '\n')
426                                 break;
427                 }
428                 if (ch != '\n') {
429                         if (autostop) {
430                                 if (output)
431                                         for (i = 0; dcharmb[i] != '\0'; i++)
432                                                 putchar(dcharmb[i]);
433                                 for (; (ch = *p) != '\n'; ++p)
434                                         (void)putchar(ch);
435                         } else
436                                 for (; (ch = *p) != '\n'; ++p);
437                 }
438                 (void)putchar('\n');
439         }
440                 free(mlbuf);
441         return (0);
442 }
443
444 static void
445 usage(void)
446 {
447         (void)fprintf(stderr, "%s\n%s\n%s\n",
448                 "usage: cut -b list [-n] [file ...]",
449                 "       cut -c list [file ...]",
450                 "       cut -f list [-s] [-d delim] [file ...]");
451         exit(1);
452 }