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