435b21070f2221d4c79ba7e1436966519c84e535
[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 "libbb.h"
13
14 /* This is a NOEXEC applet. Be very careful! */
15
16
17 /* option vars */
18 static const char optstring[] = "b:c:f:d:sn";
19 #define CUT_OPT_BYTE_FLGS       (1<<0)
20 #define CUT_OPT_CHAR_FLGS       (1<<1)
21 #define CUT_OPT_FIELDS_FLGS     (1<<2)
22 #define CUT_OPT_DELIM_FLGS      (1<<3)
23 #define CUT_OPT_SUPPRESS_FLGS (1<<4)
24
25 static char delim = '\t';       /* delimiter, default is tab */
26
27 struct cut_list {
28         int startpos;
29         int endpos;
30 };
31
32 enum {
33         BOL = 0,
34         EOL = INT_MAX,
35         NON_RANGE = -1
36 };
37
38 /* growable array holding a series of lists */
39 static struct cut_list *cut_lists;
40 static unsigned int nlists;     /* number of elements in above list */
41
42
43 static int cmpfunc(const void *a, const void *b)
44 {
45         return (((struct cut_list *) a)->startpos -
46                         ((struct cut_list *) b)->startpos);
47
48 }
49
50 static void cut_file(FILE * file)
51 {
52         char *line = NULL;
53         unsigned int linenum = 0;       /* keep these zero-based to be consistent */
54
55         /* go through every line in the file */
56         while ((line = xmalloc_getline(file)) != NULL) {
57
58                 /* set up a list so we can keep track of what's been printed */
59                 char * printed = xzalloc(strlen(line) * sizeof(char));
60                 char * orig_line = line;
61                 unsigned int cl_pos = 0;
62                 int spos;
63
64                 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
65                 if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) {
66                         /* print the chars specified in each cut list */
67                         for (; cl_pos < nlists; cl_pos++) {
68                                 spos = cut_lists[cl_pos].startpos;
69                                 while (spos < strlen(line)) {
70                                         if (!printed[spos]) {
71                                                 printed[spos] = 'X';
72                                                 putchar(line[spos]);
73                                         }
74                                         spos++;
75                                         if (spos > cut_lists[cl_pos].endpos
76                                                 || cut_lists[cl_pos].endpos == NON_RANGE)
77                                                 break;
78                                 }
79                         }
80                 } else if (delim == '\n') {     /* cut by lines */
81                         spos = cut_lists[cl_pos].startpos;
82
83                         /* get out if we have no more lists to process or if the lines
84                          * are lower than what we're interested in */
85                         if (linenum < spos || cl_pos >= nlists)
86                                 goto next_line;
87
88                         /* if the line we're looking for is lower than the one we were
89                          * passed, it means we displayed it already, so move on */
90                         while (spos < linenum) {
91                                 spos++;
92                                 /* go to the next list if we're at the end of this one */
93                                 if (spos > cut_lists[cl_pos].endpos
94                                         || cut_lists[cl_pos].endpos == NON_RANGE) {
95                                         cl_pos++;
96                                         /* get out if there's no more lists to process */
97                                         if (cl_pos >= nlists)
98                                                 goto next_line;
99                                         spos = cut_lists[cl_pos].startpos;
100                                         /* get out if the current line is lower than the one
101                                          * we just became interested in */
102                                         if (linenum < spos)
103                                                 goto next_line;
104                                 }
105                         }
106
107                         /* If we made it here, it means we've found the line we're
108                          * looking for, so print it */
109                         puts(line);
110                         goto next_line;
111                 } else {                /* cut by fields */
112                         int ndelim = -1;        /* zero-based / one-based problem */
113                         int nfields_printed = 0;
114                         char *field = NULL;
115                         const char delimiter[2] = { delim, 0 };
116
117                         /* does this line contain any delimiters? */
118                         if (strchr(line, delim) == NULL) {
119                                 if (!(option_mask32 & CUT_OPT_SUPPRESS_FLGS))
120                                         puts(line);
121                                 goto next_line;
122                         }
123
124                         /* process each list on this line, for as long as we've got
125                          * a line to process */
126                         for (; cl_pos < nlists && line; cl_pos++) {
127                                 spos = cut_lists[cl_pos].startpos;
128                                 do {
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 int cut_main(int argc, char **argv)
170 {
171         char *sopt, *ltok;
172
173         opt_complementary = "b--bcf:c--bcf:f--bcf";
174         getopt32(argc, argv, optstring, &sopt, &sopt, &sopt, &ltok);
175 //      argc -= optind;
176         argv += optind;
177         if (!(option_mask32 & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS)))
178                 bb_error_msg_and_die("expected a list of bytes, characters, or fields");
179
180         if (option_mask32 & 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 (!(option_mask32 & CUT_OPT_FIELDS_FLGS)) {
189                 if (option_mask32 & 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 = xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists));
255                         cut_lists[nlists-1].startpos = s;
256                         cut_lists[nlists-1].endpos = e;
257                 }
258
259                 /* make sure we got some cut positions out of all that */
260                 if (nlists == 0)
261                         bb_error_msg_and_die("missing list of positions");
262
263                 /* now that the lists are parsed, we need to sort them to make life
264                  * easier on us when it comes time to print the chars / fields / lines
265                  */
266                 qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
267         }
268
269         /* argv[0..argc-1] should be names of file to process. If no
270          * files were specified or '-' was specified, take input from stdin.
271          * Otherwise, we process all the files specified. */
272         if (argv[0] == NULL || LONE_DASH(argv[0])) {
273                 cut_file(stdin);
274         } else {
275                 FILE *file;
276
277                 do {
278                         file = fopen_or_warn(argv[0], "r");
279                         if (file) {
280                                 cut_file(file);
281                                 fclose(file);
282                         }
283                 } while (*++argv);
284         }
285         if (ENABLE_FEATURE_CLEAN_UP)
286                 free(cut_lists);
287         return EXIT_SUCCESS;
288 }