blob: 30dbc02dbecf0eceb8c014fa41be525abbd07e94 [file] [log] [blame]
"Robert P. J. Day"63fc1a92006-07-02 19:47:05 +00001/* vi: set sw=4 ts=4: */
Erik Andersen7ab9c7e2000-05-12 19:41:47 +00002/*
Mark Whitley807f0fd2000-08-02 18:30:11 +00003 * cut.c - minimalist version of cut
Erik Andersen7ab9c7e2000-05-12 19:41:47 +00004 *
Eric Andersen8ec10a92001-01-27 09:33:39 +00005 * Copyright (C) 1999,2000,2001 by Lineo, inc.
Eric Andersenc7bda1c2004-03-15 08:29:22 +00006 * Written by Mark Whitley <markw@codepoet.org>
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +00007 * debloated by Bernhard Fischer
Erik Andersen7ab9c7e2000-05-12 19:41:47 +00008 *
"Robert P. J. Day"801ab142006-07-12 07:56:04 +00009 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
Erik Andersen7ab9c7e2000-05-12 19:41:47 +000010 */
11
Eric Andersen3570a342000-09-25 21:45:58 +000012#include "busybox.h"
Erik Andersen7ab9c7e2000-05-12 19:41:47 +000013
Mark Whitleyb6967632001-05-18 23:04:51 +000014/* option vars */
Denis Vlasenko67b23e62006-10-03 21:00:06 +000015static const char optstring[] = "b:c:f:d:sn";
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000016#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)
Denis Vlasenko67b23e62006-10-03 21:00:06 +000021static unsigned opt;
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000022
23static char delim = '\t'; /* delimiter, default is tab */
Mark Whitleyb6967632001-05-18 23:04:51 +000024
25struct cut_list {
26 int startpos;
27 int endpos;
28};
29
Rob Landleybc68cd12006-03-10 19:22:06 +000030enum {
31 BOL = 0,
32 EOL = INT_MAX,
33 NON_RANGE = -1
34};
Mark Whitleyb6967632001-05-18 23:04:51 +000035
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000036/* growable array holding a series of lists */
37static struct cut_list *cut_lists;
38static unsigned int nlists; /* number of elements in above list */
Mark Whitleyb6967632001-05-18 23:04:51 +000039
40
41static int cmpfunc(const void *a, const void *b)
42{
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000043 return (((struct cut_list *) a)->startpos -
44 ((struct cut_list *) b)->startpos);
Mark Whitleyb6967632001-05-18 23:04:51 +000045
46}
47
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000048static void cut_file(FILE * file)
Mark Whitley807f0fd2000-08-02 18:30:11 +000049{
Mark Whitleyb6967632001-05-18 23:04:51 +000050 char *line = NULL;
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000051 unsigned int linenum = 0; /* keep these zero-based to be consistent */
Mark Whitley807f0fd2000-08-02 18:30:11 +000052
53 /* go through every line in the file */
Manuel Novoa III cad53642003-03-19 09:13:01 +000054 while ((line = bb_get_chomped_line_from_file(file)) != NULL) {
Mark Whitleyb6967632001-05-18 23:04:51 +000055
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000056 /* set up a list so we can keep track of what's been printed */
57 char * printed = xzalloc(strlen(line) * sizeof(char));
58 char * orig_line = line;
59 unsigned int cl_pos = 0;
60 int spos;
61
Mark Whitleyb6967632001-05-18 23:04:51 +000062 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000063 if ((opt & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS))) {
64 /* print the chars specified in each cut list */
65 for (; cl_pos < nlists; cl_pos++) {
66 spos = cut_lists[cl_pos].startpos;
67 while (spos < strlen(line)) {
68 if (!printed[spos]) {
69 printed[spos] = 'X';
70 putchar(line[spos]);
71 }
72 spos++;
73 if (spos > cut_lists[cl_pos].endpos
74 || cut_lists[cl_pos].endpos == NON_RANGE)
75 break;
76 }
77 }
78 } else if (delim == '\n') { /* cut by lines */
79 spos = cut_lists[cl_pos].startpos;
Mark Whitleyb6967632001-05-18 23:04:51 +000080
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +000081 /* get out if we have no more lists to process or if the lines
82 * are lower than what we're interested in */
83 if (linenum < spos || cl_pos >= nlists)
84 goto next_line;
85
86 /* if the line we're looking for is lower than the one we were
87 * passed, it means we displayed it already, so move on */
88 while (spos < linenum) {
89 spos++;
90 /* go to the next list if we're at the end of this one */
91 if (spos > cut_lists[cl_pos].endpos
92 || cut_lists[cl_pos].endpos == NON_RANGE) {
93 cl_pos++;
94 /* get out if there's no more lists to process */
95 if (cl_pos >= nlists)
96 goto next_line;
97 spos = cut_lists[cl_pos].startpos;
98 /* get out if the current line is lower than the one
99 * we just became interested in */
100 if (linenum < spos)
101 goto next_line;
102 }
103 }
104
105 /* If we made it here, it means we've found the line we're
106 * looking for, so print it */
107 puts(line);
108 goto next_line;
109 } else { /* cut by fields */
110 int ndelim = -1; /* zero-based / one-based problem */
111 int nfields_printed = 0;
112 char *field = NULL;
113 const char delimiter[2] = { delim, 0 };
114
115 /* does this line contain any delimiters? */
116 if (strchr(line, delim) == NULL) {
117 if (!(opt & CUT_OPT_SUPPRESS_FLGS))
118 puts(line);
119 goto next_line;
120 }
121
122 /* process each list on this line, for as long as we've got
123 * a line to process */
124 for (; cl_pos < nlists && line; cl_pos++) {
125 spos = cut_lists[cl_pos].startpos;
126 do {
127
128 /* find the field we're looking for */
129 while (line && ndelim < spos) {
130 field = strsep(&line, delimiter);
131 ndelim++;
132 }
133
134 /* we found it, and it hasn't been printed yet */
135 if (field && ndelim == spos && !printed[ndelim]) {
136 /* if this isn't our first time through, we need to
137 * print the delimiter after the last field that was
138 * printed */
139 if (nfields_printed > 0)
140 putchar(delim);
141 fputs(field, stdout);
142 printed[ndelim] = 'X';
143 nfields_printed++; /* shouldn't overflow.. */
144 }
145
146 spos++;
147
148 /* keep going as long as we have a line to work with,
149 * this is a list, and we're not at the end of that
150 * list */
151 } while (spos <= cut_lists[cl_pos].endpos && line
152 && cut_lists[cl_pos].endpos != NON_RANGE);
153 }
Mark Whitley807f0fd2000-08-02 18:30:11 +0000154 }
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000155 /* if we printed anything at all, we need to finish it with a
156 * newline cuz we were handed a chomped line */
157 putchar('\n');
158 next_line:
Mark Whitleyb6967632001-05-18 23:04:51 +0000159 linenum++;
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000160 free(printed);
161 free(orig_line);
Mark Whitley807f0fd2000-08-02 18:30:11 +0000162 }
163}
164
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000165static int getval(char *ntok)
166{
167 char *junk;
168 int i = strtoul(ntok, &junk, 10);
169
170 if (*junk != '\0' || i < 0)
171 bb_error_msg_and_die("invalid byte or field list");
172 return i;
173}
174
175static const char * const _op_on_field = " only when operating on fields";
Mark Whitleyb6967632001-05-18 23:04:51 +0000176
Rob Landleydfba7412006-03-06 20:47:33 +0000177int cut_main(int argc, char **argv)
Mark Whitley807f0fd2000-08-02 18:30:11 +0000178{
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000179 char *sopt, *ltok;
Mark Whitley807f0fd2000-08-02 18:30:11 +0000180
Denis Vlasenko67b23e62006-10-03 21:00:06 +0000181 opt_complementary = "b--bcf:c--bcf:f--bcf";
182 opt = getopt32(argc, argv, optstring, &sopt, &sopt, &sopt, &ltok);
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000183 if (!(opt & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS)))
184 bb_error_msg_and_die
185 ("expected a list of bytes, characters, or fields");
186 if (opt & BB_GETOPT_ERROR)
Eric Andersenc9e70242003-06-20 09:16:00 +0000187 bb_error_msg_and_die("only one type of list may be specified");
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000188
189 if ((opt & (CUT_OPT_DELIM_FLGS))) {
190 if (strlen(ltok) > 1) {
Eric Andersenc9e70242003-06-20 09:16:00 +0000191 bb_error_msg_and_die("the delimiter must be a single character");
192 }
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000193 delim = ltok[0];
Mark Whitley807f0fd2000-08-02 18:30:11 +0000194 }
Mark Whitley807f0fd2000-08-02 18:30:11 +0000195
Mark Whitleyb6967632001-05-18 23:04:51 +0000196 /* non-field (char or byte) cutting has some special handling */
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000197 if (!(opt & CUT_OPT_FIELDS_FLGS)) {
198 if (opt & CUT_OPT_SUPPRESS_FLGS) {
199 bb_error_msg_and_die
200 ("suppressing non-delimited lines makes sense%s",
201 _op_on_field);
Mark Whitleyb6967632001-05-18 23:04:51 +0000202 }
Eric Andersen8876fb22003-06-20 09:01:58 +0000203 if (delim != '\t') {
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000204 bb_error_msg_and_die
205 ("a delimiter may be specified%s", _op_on_field);
Mark Whitleyb6967632001-05-18 23:04:51 +0000206 }
Mark Whitley807f0fd2000-08-02 18:30:11 +0000207 }
208
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000209 /*
210 * parse list and put values into startpos and endpos.
211 * valid list formats: N, N-, N-M, -M
212 * more than one list can be separated by commas
213 */
214 {
215 char *ntok;
216 int s = 0, e = 0;
217
218 /* take apart the lists, one by one (they are separated with commas */
219 while ((ltok = strsep(&sopt, ",")) != NULL) {
220
221 /* it's actually legal to pass an empty list */
222 if (strlen(ltok) == 0)
223 continue;
224
225 /* get the start pos */
226 ntok = strsep(&ltok, "-");
227 if (ntok == NULL) {
228 bb_error_msg
229 ("internal error: ntok is null for start pos!?\n");
230 } else if (strlen(ntok) == 0) {
231 s = BOL;
232 } else {
233 s = getval(ntok);
234 /* account for the fact that arrays are zero based, while
235 * the user expects the first char on the line to be char #1 */
236 if (s != 0)
237 s--;
238 }
239
240 /* get the end pos */
241 ntok = strsep(&ltok, "-");
242 if (ntok == NULL) {
243 e = NON_RANGE;
244 } else if (strlen(ntok) == 0) {
245 e = EOL;
246 } else {
247 e = getval(ntok);
248 /* if the user specified and end position of 0, that means "til the
249 * end of the line */
250 if (e == 0)
251 e = EOL;
252 e--; /* again, arrays are zero based, lines are 1 based */
253 if (e == s)
254 e = NON_RANGE;
255 }
256
257 /* if there's something left to tokenize, the user passed
258 * an invalid list */
259 if (ltok)
260 bb_error_msg_and_die("invalid byte or field list");
261
262 /* add the new list */
263 cut_lists =
264 xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists));
265 cut_lists[nlists - 1].startpos = s;
266 cut_lists[nlists - 1].endpos = e;
267 }
268
269 /* make sure we got some cut positions out of all that */
270 if (nlists == 0)
271 bb_error_msg_and_die("missing list of positions");
272
273 /* now that the lists are parsed, we need to sort them to make life
274 * easier on us when it comes time to print the chars / fields / lines
275 */
276 qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc);
277 }
278
Mark Whitley807f0fd2000-08-02 18:30:11 +0000279 /* argv[(optind)..(argc-1)] should be names of file to process. If no
280 * files were specified or '-' was specified, take input from stdin.
281 * Otherwise, we process all the files specified. */
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000282 if (argv[optind] == NULL
283 || (argv[optind][0] == '-' && argv[optind][1] == '\0')) {
Mark Whitley807f0fd2000-08-02 18:30:11 +0000284 cut_file(stdin);
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000285 } else {
Mark Whitley807f0fd2000-08-02 18:30:11 +0000286 FILE *file;
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000287
288 for (; optind < argc; optind++) {
289 file = bb_wfopen(argv[optind], "r");
290 if (file) {
Mark Whitley807f0fd2000-08-02 18:30:11 +0000291 cut_file(file);
292 fclose(file);
293 }
294 }
295 }
Bernhard Reutner-Fischer73561cc2006-08-28 23:31:54 +0000296 if (ENABLE_FEATURE_CLEAN_UP)
297 free(cut_lists);
Matt Kraai3e856ce2000-12-01 02:55:13 +0000298 return EXIT_SUCCESS;
Mark Whitley807f0fd2000-08-02 18:30:11 +0000299}