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