This commit was manufactured by cvs2svn to create tag 'busybox_1_00'.
[oweals/busybox.git] / busybox / coreutils / cut.c
1 /* vi: set sw=8 ts=8: */
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  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  *
22  */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <getopt.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <limits.h>
30 #include "busybox.h"
31
32
33 /* option vars */
34 static const char optstring[] = "b:c:f:d:sn";
35 #define OPT_BYTE_FLGS    1
36 #define OPT_CHAR_FLGS    2
37 #define OPT_FIELDS_FLGS  4
38 #define OPT_DELIM_FLGS   8
39 #define OPT_SUPRESS_FLGS 16
40 static char part; /* (b)yte, (c)har, (f)ields */
41 static unsigned int supress_non_delimited_lines;
42 static char delim = '\t'; /* delimiter, default is tab */
43
44 struct cut_list {
45         int startpos;
46         int endpos;
47 };
48
49 static const int BOL = 0;
50 static const int EOL = INT_MAX;
51 static const int NON_RANGE = -1;
52
53 static struct cut_list *cut_lists = NULL; /* growable array holding a series of lists */
54 static unsigned int nlists = 0; /* number of elements in above list */
55
56
57 static int cmpfunc(const void *a, const void *b)
58 {
59         struct cut_list *la = (struct cut_list *)a;
60         struct cut_list *lb = (struct cut_list *)b;
61
62         if (la->startpos > lb->startpos)
63                 return 1;
64         if (la->startpos < lb->startpos)
65                 return -1;
66         return 0;
67 }
68
69
70 /*
71  * parse_lists() - parses a list and puts values into startpos and endpos.
72  * valid list formats: N, N-, N-M, -M
73  * more than one list can be separated by commas
74  */
75 static void parse_lists(char *lists)
76 {
77         char *ltok = NULL;
78         char *ntok = NULL;
79         char *junk;
80         int s = 0, e = 0;
81
82         /* take apart the lists, one by one (they are separated with commas */
83         while ((ltok = strsep(&lists, ",")) != NULL) {
84
85                 /* it's actually legal to pass an empty list */
86                 if (strlen(ltok) == 0)
87                         continue;
88
89                 /* get the start pos */
90                 ntok = strsep(&ltok, "-");
91                 if (ntok == NULL) {
92                         fprintf(stderr, "Help ntok is null for starting position! What do I do?\n");
93                 } else if (strlen(ntok) == 0) {
94                         s = BOL;
95                 } else {
96                         s = strtoul(ntok, &junk, 10);
97                         if(*junk != '\0' || s < 0)
98                                 bb_error_msg_and_die("invalid byte or field list");
99
100                         /* account for the fact that arrays are zero based, while the user
101                          * expects the first char on the line to be char # 1 */
102                         if (s != 0)
103                                 s--;
104                 }
105
106                 /* get the end pos */
107                 ntok = strsep(&ltok, "-");
108                 if (ntok == NULL) {
109                         e = NON_RANGE;
110                 } else if (strlen(ntok) == 0) {
111                         e = EOL;
112                 } else {
113                         e = strtoul(ntok, &junk, 10);
114                         if(*junk != '\0' || e < 0)
115                                 bb_error_msg_and_die("invalid byte or field list");
116                         /* if the user specified and end position of 0, that means "til the
117                          * end of the line */
118                         if (e == 0)
119                                 e = INT_MAX;
120                         e--; /* again, arrays are zero based, lines are 1 based */
121                         if (e == s)
122                                 e = NON_RANGE;
123                 }
124
125                 /* if there's something left to tokenize, the user past an invalid list */
126                 if (ltok)
127                         bb_error_msg_and_die("invalid byte or field list");
128
129                 /* add the new list */
130                 cut_lists = xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists));
131                 cut_lists[nlists-1].startpos = s;
132                 cut_lists[nlists-1].endpos = e;
133         }
134
135         /* make sure we got some cut positions out of all that */
136         if (nlists == 0)
137                 bb_error_msg_and_die("missing list of positions");
138
139         /* now that the lists are parsed, we need to sort them to make life easier
140          * on us when it comes time to print the chars / fields / lines */
141         qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
142
143 }
144
145
146 static void cut_line_by_chars(const char *line)
147 {
148         int c, l;
149         /* set up a list so we can keep track of what's been printed */
150         char *printed = xcalloc(strlen(line), sizeof(char));
151
152         /* print the chars specified in each cut list */
153         for (c = 0; c < nlists; c++) {
154                 l = cut_lists[c].startpos;
155                 while (l < strlen(line)) {
156                         if (!printed[l]) {
157                                 putchar(line[l]);
158                                 printed[l] = 'X';
159                         }
160                         l++;
161                         if (cut_lists[c].endpos == NON_RANGE || l > cut_lists[c].endpos)
162                                 break;
163                 }
164         }
165         putchar('\n'); /* cuz we were handed a chomped line */
166         free(printed);
167 }
168
169
170 static void cut_line_by_fields(char *line)
171 {
172         int c, f;
173         int ndelim = -1; /* zero-based / one-based problem */
174         int nfields_printed = 0;
175         char *field = NULL;
176         char d[2] = { delim, 0 };
177         char *printed;
178
179         /* test the easy case first: does this line contain any delimiters? */
180         if (strchr(line, delim) == NULL) {
181                 if (!supress_non_delimited_lines)
182                         puts(line);
183                 return;
184         }
185
186         /* set up a list so we can keep track of what's been printed */
187         printed = xcalloc(strlen(line), sizeof(char));
188
189         /* process each list on this line, for as long as we've got a line to process */
190         for (c = 0; c < nlists && line; c++) {
191                 f = cut_lists[c].startpos;
192                 do {
193
194                         /* find the field we're looking for */
195                         while (line && ndelim < f) {
196                                 field = strsep(&line, d);
197                                 ndelim++;
198                         }
199
200                         /* we found it, and it hasn't been printed yet */
201                         if (field && ndelim == f && !printed[ndelim]) {
202                                 /* if this isn't our first time through, we need to print the
203                                  * delimiter after the last field that was printed */
204                                 if (nfields_printed > 0)
205                                         putchar(delim);
206                                 fputs(field, stdout);
207                                 printed[ndelim] = 'X';
208                                 nfields_printed++;
209                         }
210
211                         f++;
212
213                         /* keep going as long as we have a line to work with, this is a
214                          * list, and we're not at the end of that list */
215                 } while (line && cut_lists[c].endpos != NON_RANGE && f <= cut_lists[c].endpos);
216         }
217
218         /* if we printed anything at all, we need to finish it with a newline cuz
219          * we were handed a chomped line */
220         putchar('\n');
221
222         free(printed);
223 }
224
225
226 static void cut_file_by_lines(const char *line, unsigned int linenum)
227 {
228         static int c = 0;
229         static int l = -1;
230
231         /* I can't initialize this above cuz the "initializer isn't
232          * constant" *sigh* */
233         if (l == -1)
234                 l = cut_lists[c].startpos;
235
236         /* get out if we have no more lists to process or if the lines are lower
237          * than what we're interested in */
238         if (c >= nlists || linenum < l)
239                 return;
240
241         /* if the line we're looking for is lower than the one we were passed, it
242          * means we displayed it already, so move on */
243         while (l < linenum) {
244                 l++;
245                 /* move on to the next list if we're at the end of this one */
246                 if (cut_lists[c].endpos == NON_RANGE || l > cut_lists[c].endpos) {
247                         c++;
248                         /* get out if there's no more lists to process */
249                         if (c >= nlists)
250                                 return;
251                         l = cut_lists[c].startpos;
252                         /* get out if the current line is lower than the one we just became
253                          * interested in */
254                         if (linenum < l)
255                                 return;
256                 }
257         }
258
259         /* If we made it here, it means we've found the line we're looking for, so print it */
260         puts(line);
261 }
262
263
264 /*
265  * snippy-snip
266  */
267 static void cut_file(FILE *file)
268 {
269         char *line = NULL;
270         unsigned int linenum = 0; /* keep these zero-based to be consistent */
271
272         /* go through every line in the file */
273         while ((line = bb_get_chomped_line_from_file(file)) != NULL) {
274
275                 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
276                 if ((part & (OPT_CHAR_FLGS | OPT_BYTE_FLGS)))
277                         cut_line_by_chars(line);
278
279                 /* cut based on fields */
280                 else {
281                         if (delim == '\n')
282                                 cut_file_by_lines(line, linenum);
283                         else
284                                 cut_line_by_fields(line);
285                 }
286
287                 linenum++;
288                 free(line);
289         }
290 }
291
292
293 extern int cut_main(int argc, char **argv)
294 {
295         unsigned long opt;
296         char *sopt, *sdopt;
297
298         bb_opt_complementaly = "b~bcf:c~bcf:f~bcf";
299         opt = bb_getopt_ulflags(argc, argv, optstring, &sopt, &sopt, &sopt, &sdopt);
300         part = opt & (OPT_BYTE_FLGS|OPT_CHAR_FLGS|OPT_FIELDS_FLGS);
301         if(part == 0)
302                 bb_error_msg_and_die("you must specify a list of bytes, characters, or fields");
303         if(opt & 0x80000000UL)
304                 bb_error_msg_and_die("only one type of list may be specified");
305         parse_lists(sopt);
306         if((opt & (OPT_DELIM_FLGS))) {
307                 if (strlen(sdopt) > 1) {
308                         bb_error_msg_and_die("the delimiter must be a single character");
309                 }
310                 delim = sdopt[0];
311         }
312         supress_non_delimited_lines = opt & OPT_SUPRESS_FLGS;
313
314         /*  non-field (char or byte) cutting has some special handling */
315         if (part != OPT_FIELDS_FLGS) {
316                 if (supress_non_delimited_lines) {
317                         bb_error_msg_and_die("suppressing non-delimited lines makes sense"
318                                         " only when operating on fields");
319                 }
320                 if (delim != '\t') {
321                         bb_error_msg_and_die("a delimiter may be specified only when operating on fields");
322                 }
323         }
324
325         /* argv[(optind)..(argc-1)] should be names of file to process. If no
326          * files were specified or '-' was specified, take input from stdin.
327          * Otherwise, we process all the files specified. */
328         if (argv[optind] == NULL || (strcmp(argv[optind], "-") == 0)) {
329                 cut_file(stdin);
330         }
331         else {
332                 int i;
333                 FILE *file;
334                 for (i = optind; i < argc; i++) {
335                         file = bb_wfopen(argv[i], "r");
336                         if(file) {
337                                 cut_file(file);
338                                 fclose(file);
339                         }
340                 }
341         }
342
343         return EXIT_SUCCESS;
344 }