1b80e7e73687e968d95e39723579599235f39d98
[oweals/busybox.git] / coreutils / cut.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * cut.c - minimalist version of cut
4  *
5  * Copyright (C) 1999,2000,2001 by Lineo, inc.
6  * Written by Mark Whitley <markw@codepoet.org>
7  *
8  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
9  */
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <limits.h>
16 #include "busybox.h"
17
18
19 /* option vars */
20 static const char optstring[] = "b:c:f:d:sn";
21 #define OPT_BYTE_FLGS    1
22 #define OPT_CHAR_FLGS    2
23 #define OPT_FIELDS_FLGS  4
24 #define OPT_DELIM_FLGS   8
25 #define OPT_SUPRESS_FLGS 16
26 static char part; /* (b)yte, (c)har, (f)ields */
27 static unsigned int supress_non_delimited_lines;
28 static char delim = '\t'; /* delimiter, default is tab */
29
30 struct cut_list {
31         int startpos;
32         int endpos;
33 };
34
35 enum {
36         BOL = 0,
37         EOL = INT_MAX,
38         NON_RANGE = -1
39 };
40
41 static struct cut_list *cut_lists = NULL; /* growable array holding a series of lists */
42 static unsigned int nlists = 0; /* number of elements in above list */
43
44
45 static int cmpfunc(const void *a, const void *b)
46 {
47         struct cut_list *la = (struct cut_list *)a;
48         struct cut_list *lb = (struct cut_list *)b;
49
50         if (la->startpos > lb->startpos)
51                 return 1;
52         if (la->startpos < lb->startpos)
53                 return -1;
54         return 0;
55 }
56
57
58 /*
59  * parse_lists() - parses a list and puts values into startpos and endpos.
60  * valid list formats: N, N-, N-M, -M
61  * more than one list can be separated by commas
62  */
63 static void parse_lists(char *lists)
64 {
65         char *ltok = NULL;
66         char *ntok = NULL;
67         char *junk;
68         int s = 0, e = 0;
69
70         /* take apart the lists, one by one (they are separated with commas */
71         while ((ltok = strsep(&lists, ",")) != NULL) {
72
73                 /* it's actually legal to pass an empty list */
74                 if (strlen(ltok) == 0)
75                         continue;
76
77                 /* get the start pos */
78                 ntok = strsep(&ltok, "-");
79                 if (ntok == NULL) {
80                         fprintf(stderr, "Help ntok is null for starting position! What do I do?\n");
81                 } else if (strlen(ntok) == 0) {
82                         s = BOL;
83                 } else {
84                         s = strtoul(ntok, &junk, 10);
85                         if(*junk != '\0' || s < 0)
86                                 bb_error_msg_and_die("invalid byte or field list");
87
88                         /* account for the fact that arrays are zero based, while the user
89                          * expects the first char on the line to be char # 1 */
90                         if (s != 0)
91                                 s--;
92                 }
93
94                 /* get the end pos */
95                 ntok = strsep(&ltok, "-");
96                 if (ntok == NULL) {
97                         e = NON_RANGE;
98                 } else if (strlen(ntok) == 0) {
99                         e = EOL;
100                 } else {
101                         e = strtoul(ntok, &junk, 10);
102                         if(*junk != '\0' || e < 0)
103                                 bb_error_msg_and_die("invalid byte or field list");
104                         /* if the user specified and end position of 0, that means "til the
105                          * end of the line */
106                         if (e == 0)
107                                 e = INT_MAX;
108                         e--; /* again, arrays are zero based, lines are 1 based */
109                         if (e == s)
110                                 e = NON_RANGE;
111                 }
112
113                 /* if there's something left to tokenize, the user past an invalid list */
114                 if (ltok)
115                         bb_error_msg_and_die("invalid byte or field list");
116
117                 /* add the new list */
118                 cut_lists = xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists));
119                 cut_lists[nlists-1].startpos = s;
120                 cut_lists[nlists-1].endpos = e;
121         }
122
123         /* make sure we got some cut positions out of all that */
124         if (nlists == 0)
125                 bb_error_msg_and_die("missing list of positions");
126
127         /* now that the lists are parsed, we need to sort them to make life easier
128          * on us when it comes time to print the chars / fields / lines */
129         qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
130
131 }
132
133
134 static void cut_line_by_chars(const char *line)
135 {
136         int c, l;
137         /* set up a list so we can keep track of what's been printed */
138         char *printed = xzalloc(strlen(line));
139
140         /* print the chars specified in each cut list */
141         for (c = 0; c < nlists; c++) {
142                 l = cut_lists[c].startpos;
143                 while (l < strlen(line)) {
144                         if (!printed[l]) {
145                                 putchar(line[l]);
146                                 printed[l] = 'X';
147                         }
148                         l++;
149                         if (cut_lists[c].endpos == NON_RANGE || l > cut_lists[c].endpos)
150                                 break;
151                 }
152         }
153         putchar('\n'); /* cuz we were handed a chomped line */
154         free(printed);
155 }
156
157
158 static void cut_line_by_fields(char *line)
159 {
160         int c, f;
161         int ndelim = -1; /* zero-based / one-based problem */
162         int nfields_printed = 0;
163         char *field = NULL;
164         char d[2] = { delim, 0 };
165         char *printed;
166
167         /* test the easy case first: does this line contain any delimiters? */
168         if (strchr(line, delim) == NULL) {
169                 if (!supress_non_delimited_lines)
170                         puts(line);
171                 return;
172         }
173
174         /* set up a list so we can keep track of what's been printed */
175         printed = xzalloc(strlen(line));
176
177         /* process each list on this line, for as long as we've got a line to process */
178         for (c = 0; c < nlists && line; c++) {
179                 f = cut_lists[c].startpos;
180                 do {
181
182                         /* find the field we're looking for */
183                         while (line && ndelim < f) {
184                                 field = strsep(&line, d);
185                                 ndelim++;
186                         }
187
188                         /* we found it, and it hasn't been printed yet */
189                         if (field && ndelim == f && !printed[ndelim]) {
190                                 /* if this isn't our first time through, we need to print the
191                                  * delimiter after the last field that was printed */
192                                 if (nfields_printed > 0)
193                                         putchar(delim);
194                                 fputs(field, stdout);
195                                 printed[ndelim] = 'X';
196                                 nfields_printed++;
197                         }
198
199                         f++;
200
201                         /* keep going as long as we have a line to work with, this is a
202                          * list, and we're not at the end of that list */
203                 } while (line && cut_lists[c].endpos != NON_RANGE && f <= cut_lists[c].endpos);
204         }
205
206         /* if we printed anything at all, we need to finish it with a newline cuz
207          * we were handed a chomped line */
208         putchar('\n');
209
210         free(printed);
211 }
212
213
214 static void cut_file_by_lines(const char *line, unsigned int linenum)
215 {
216         static int c = 0;
217         static int l = -1;
218
219         /* I can't initialize this above cuz the "initializer isn't
220          * constant" *sigh* */
221         if (l == -1)
222                 l = cut_lists[c].startpos;
223
224         /* get out if we have no more lists to process or if the lines are lower
225          * than what we're interested in */
226         if (c >= nlists || linenum < l)
227                 return;
228
229         /* if the line we're looking for is lower than the one we were passed, it
230          * means we displayed it already, so move on */
231         while (l < linenum) {
232                 l++;
233                 /* move on to the next list if we're at the end of this one */
234                 if (cut_lists[c].endpos == NON_RANGE || l > cut_lists[c].endpos) {
235                         c++;
236                         /* get out if there's no more lists to process */
237                         if (c >= nlists)
238                                 return;
239                         l = cut_lists[c].startpos;
240                         /* get out if the current line is lower than the one we just became
241                          * interested in */
242                         if (linenum < l)
243                                 return;
244                 }
245         }
246
247         /* If we made it here, it means we've found the line we're looking for, so print it */
248         puts(line);
249 }
250
251
252 /*
253  * snippy-snip
254  */
255 static void cut_file(FILE *file)
256 {
257         char *line = NULL;
258         unsigned int linenum = 0; /* keep these zero-based to be consistent */
259
260         /* go through every line in the file */
261         while ((line = bb_get_chomped_line_from_file(file)) != NULL) {
262
263                 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
264                 if ((part & (OPT_CHAR_FLGS | OPT_BYTE_FLGS)))
265                         cut_line_by_chars(line);
266
267                 /* cut based on fields */
268                 else {
269                         if (delim == '\n')
270                                 cut_file_by_lines(line, linenum);
271                         else
272                                 cut_line_by_fields(line);
273                 }
274
275                 linenum++;
276                 free(line);
277         }
278 }
279
280
281 int cut_main(int argc, char **argv)
282 {
283         unsigned long opt;
284         char *sopt, *sdopt;
285
286         bb_opt_complementally = "b--bcf:c--bcf:f--bcf";
287         opt = bb_getopt_ulflags(argc, argv, optstring, &sopt, &sopt, &sopt, &sdopt);
288         part = opt & (OPT_BYTE_FLGS|OPT_CHAR_FLGS|OPT_FIELDS_FLGS);
289         if(part == 0)
290                 bb_error_msg_and_die("you must specify a list of bytes, characters, or fields");
291         if(opt & BB_GETOPT_ERROR)
292                 bb_error_msg_and_die("only one type of list may be specified");
293         parse_lists(sopt);
294         if((opt & (OPT_DELIM_FLGS))) {
295                 if (strlen(sdopt) > 1) {
296                         bb_error_msg_and_die("the delimiter must be a single character");
297                 }
298                 delim = sdopt[0];
299         }
300         supress_non_delimited_lines = opt & OPT_SUPRESS_FLGS;
301
302         /*  non-field (char or byte) cutting has some special handling */
303         if (part != OPT_FIELDS_FLGS) {
304                 if (supress_non_delimited_lines) {
305                         bb_error_msg_and_die("suppressing non-delimited lines makes sense"
306                                         " only when operating on fields");
307                 }
308                 if (delim != '\t') {
309                         bb_error_msg_and_die("a delimiter may be specified only when operating on fields");
310                 }
311         }
312
313         /* argv[(optind)..(argc-1)] should be names of file to process. If no
314          * files were specified or '-' was specified, take input from stdin.
315          * Otherwise, we process all the files specified. */
316         if (argv[optind] == NULL || (strcmp(argv[optind], "-") == 0)) {
317                 cut_file(stdin);
318         }
319         else {
320                 int i;
321                 FILE *file;
322                 for (i = optind; i < argc; i++) {
323                         file = bb_wfopen(argv[i], "r");
324                         if(file) {
325                                 cut_file(file);
326                                 fclose(file);
327                         }
328                 }
329         }
330
331         return EXIT_SUCCESS;
332 }