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