attempt to regularize atoi mess.
[platform/upstream/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 const char _op_on_field[] = " only when operating on fields";
167
168 int cut_main(int argc, char **argv)
169 {
170         char *sopt, *ltok;
171
172         opt_complementary = "b--bcf:c--bcf:f--bcf";
173         opt = getopt32(argc, argv, optstring, &sopt, &sopt, &sopt, &ltok);
174         if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS)))
175                 bb_error_msg_and_die
176                         ("expected a list of bytes, characters, or fields");
177         if (opt & BB_GETOPT_ERROR)
178                 bb_error_msg_and_die("only one type of list may be specified");
179
180         if ((opt & (CUT_OPT_DELIM_FLGS))) {
181                 if (strlen(ltok) > 1) {
182                         bb_error_msg_and_die("the delimiter must be a single character");
183                 }
184                 delim = ltok[0];
185         }
186
187         /*  non-field (char or byte) cutting has some special handling */
188         if (!(opt & CUT_OPT_FIELDS_FLGS)) {
189                 if (opt & CUT_OPT_SUPPRESS_FLGS) {
190                         bb_error_msg_and_die
191                                 ("suppressing non-delimited lines makes sense%s",
192                                  _op_on_field);
193                 }
194                 if (delim != '\t') {
195                         bb_error_msg_and_die
196                                 ("a delimiter may be specified%s", _op_on_field);
197                 }
198         }
199
200         /*
201          * parse list and put values into startpos and endpos.
202          * valid list formats: N, N-, N-M, -M
203          * more than one list can be separated by commas
204          */
205         {
206                 char *ntok;
207                 int s = 0, e = 0;
208
209                 /* take apart the lists, one by one (they are separated with commas */
210                 while ((ltok = strsep(&sopt, ",")) != NULL) {
211
212                         /* it's actually legal to pass an empty list */
213                         if (strlen(ltok) == 0)
214                                 continue;
215
216                         /* get the start pos */
217                         ntok = strsep(&ltok, "-");
218                         if (ntok == NULL) {
219                                 bb_error_msg
220                                         ("internal error: ntok is null for start pos!?\n");
221                         } else if (strlen(ntok) == 0) {
222                                 s = BOL;
223                         } else {
224                                 s = xatoi_u(ntok);
225                                 /* account for the fact that arrays are zero based, while
226                                  * the user expects the first char on the line to be char #1 */
227                                 if (s != 0)
228                                         s--;
229                         }
230
231                         /* get the end pos */
232                         ntok = strsep(&ltok, "-");
233                         if (ntok == NULL) {
234                                 e = NON_RANGE;
235                         } else if (strlen(ntok) == 0) {
236                                 e = EOL;
237                         } else {
238                                 e = xatoi_u(ntok);
239                                 /* if the user specified and end position of 0, that means "til the
240                                  * end of the line */
241                                 if (e == 0)
242                                         e = EOL;
243                                 e--;    /* again, arrays are zero based, lines are 1 based */
244                                 if (e == s)
245                                         e = NON_RANGE;
246                         }
247
248                         /* if there's something left to tokenize, the user passed
249                          * an invalid list */
250                         if (ltok)
251                                 bb_error_msg_and_die("invalid byte or field list");
252
253                         /* add the new list */
254                         cut_lists =
255                                 xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists));
256                         cut_lists[nlists - 1].startpos = s;
257                         cut_lists[nlists - 1].endpos = e;
258                 }
259
260                 /* make sure we got some cut positions out of all that */
261                 if (nlists == 0)
262                         bb_error_msg_and_die("missing list of positions");
263
264                 /* now that the lists are parsed, we need to sort them to make life
265                  * easier on us when it comes time to print the chars / fields / lines
266                  */
267                 qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
268         }
269
270         /* argv[(optind)..(argc-1)] should be names of file to process. If no
271          * files were specified or '-' was specified, take input from stdin.
272          * Otherwise, we process all the files specified. */
273         if (argv[optind] == NULL
274                 || (argv[optind][0] == '-' && argv[optind][1] == '\0')) {
275                 cut_file(stdin);
276         } else {
277                 FILE *file;
278
279                 for (; optind < argc; optind++) {
280                         file = bb_wfopen(argv[optind], "r");
281                         if (file) {
282                                 cut_file(file);
283                                 fclose(file);
284                         }
285                 }
286         }
287         if (ENABLE_FEATURE_CLEAN_UP)
288                 free(cut_lists);
289         return EXIT_SUCCESS;
290 }