1 /* vi: set sw=4 ts=4: */
3 * cut.c - minimalist version of cut
5 * Copyright (C) 1999,2000,2001 by Lineo, inc.
6 * Written by Mark Whitley <markw@codepoet.org>
7 * debloated by Bernhard Reutner-Fischer
9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 //usage:#define cut_trivial_usage
13 //usage: "[OPTIONS] [FILE]..."
14 //usage:#define cut_full_usage "\n\n"
15 //usage: "Print selected fields from each input FILE to stdout\n"
16 //usage: "\n -b LIST Output only bytes from LIST"
17 //usage: "\n -c LIST Output only characters from LIST"
18 //usage: "\n -d CHAR Use CHAR instead of tab as the field delimiter"
19 //usage: "\n -s Output only the lines containing delimiter"
20 //usage: "\n -f N Print only these fields"
21 //usage: "\n -n Ignored"
23 //usage:#define cut_example_usage
24 //usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n"
26 //usage: "$ echo \"Hello world\" | cut -f 2 -d ' '\n"
31 /* This is a NOEXEC applet. Be very careful! */
35 static const char optstring
[] ALIGN1
= "b:c:f:d:sn";
36 #define CUT_OPT_BYTE_FLGS (1 << 0)
37 #define CUT_OPT_CHAR_FLGS (1 << 1)
38 #define CUT_OPT_FIELDS_FLGS (1 << 2)
39 #define CUT_OPT_DELIM_FLGS (1 << 3)
40 #define CUT_OPT_SUPPRESS_FLGS (1 << 4)
53 static int cmpfunc(const void *a
, const void *b
)
55 return (((struct cut_list
*) a
)->startpos
-
56 ((struct cut_list
*) b
)->startpos
);
59 static void cut_file(FILE *file
, char delim
, const struct cut_list
*cut_lists
, unsigned nlists
)
62 unsigned linenum
= 0; /* keep these zero-based to be consistent */
64 /* go through every line in the file */
65 while ((line
= xmalloc_fgetline(file
)) != NULL
) {
67 /* set up a list so we can keep track of what's been printed */
68 int linelen
= strlen(line
);
69 char *printed
= xzalloc(linelen
+ 1);
70 char *orig_line
= line
;
74 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
75 if (option_mask32
& (CUT_OPT_CHAR_FLGS
| CUT_OPT_BYTE_FLGS
)) {
76 /* print the chars specified in each cut list */
77 for (; cl_pos
< nlists
; cl_pos
++) {
78 spos
= cut_lists
[cl_pos
].startpos
;
79 while (spos
< linelen
) {
85 if (spos
> cut_lists
[cl_pos
].endpos
86 /* NON_RANGE is -1, so if below is true,
87 * the above was true too (spos is >= 0) */
88 /* || cut_lists[cl_pos].endpos == NON_RANGE */
94 } else if (delim
== '\n') { /* cut by lines */
95 spos
= cut_lists
[cl_pos
].startpos
;
97 /* get out if we have no more lists to process or if the lines
98 * are lower than what we're interested in */
99 if (((int)linenum
< spos
) || (cl_pos
>= nlists
))
102 /* if the line we're looking for is lower than the one we were
103 * passed, it means we displayed it already, so move on */
104 while (spos
< (int)linenum
) {
106 /* go to the next list if we're at the end of this one */
107 if (spos
> cut_lists
[cl_pos
].endpos
108 || cut_lists
[cl_pos
].endpos
== NON_RANGE
111 /* get out if there's no more lists to process */
112 if (cl_pos
>= nlists
)
114 spos
= cut_lists
[cl_pos
].startpos
;
115 /* get out if the current line is lower than the one
116 * we just became interested in */
117 if ((int)linenum
< spos
)
122 /* If we made it here, it means we've found the line we're
123 * looking for, so print it */
126 } else { /* cut by fields */
127 int ndelim
= -1; /* zero-based / one-based problem */
128 int nfields_printed
= 0;
132 delimiter
[0] = delim
;
135 /* does this line contain any delimiters? */
136 if (strchr(line
, delim
) == NULL
) {
137 if (!(option_mask32
& CUT_OPT_SUPPRESS_FLGS
))
142 /* process each list on this line, for as long as we've got
143 * a line to process */
144 for (; cl_pos
< nlists
&& line
; cl_pos
++) {
145 spos
= cut_lists
[cl_pos
].startpos
;
147 /* find the field we're looking for */
148 while (line
&& ndelim
< spos
) {
149 field
= strsep(&line
, delimiter
);
153 /* we found it, and it hasn't been printed yet */
154 if (field
&& ndelim
== spos
&& !printed
[ndelim
]) {
155 /* if this isn't our first time through, we need to
156 * print the delimiter after the last field that was
158 if (nfields_printed
> 0)
160 fputs(field
, stdout
);
161 printed
[ndelim
] = 'X';
162 nfields_printed
++; /* shouldn't overflow.. */
167 /* keep going as long as we have a line to work with,
168 * this is a list, and we're not at the end of that
170 } while (spos
<= cut_lists
[cl_pos
].endpos
&& line
171 && cut_lists
[cl_pos
].endpos
!= NON_RANGE
);
174 /* if we printed anything at all, we need to finish it with a
175 * newline cuz we were handed a chomped line */
184 int cut_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
185 int cut_main(int argc UNUSED_PARAM
, char **argv
)
187 /* growable array holding a series of lists */
188 struct cut_list
*cut_lists
= NULL
;
189 unsigned nlists
= 0; /* number of elements in above list */
190 char delim
= '\t'; /* delimiter, default is tab */
194 opt_complementary
= "b--bcf:c--bcf:f--bcf";
195 opt
= getopt32(argv
, optstring
, &sopt
, &sopt
, &sopt
, <ok
);
198 if (!(opt
& (CUT_OPT_BYTE_FLGS
| CUT_OPT_CHAR_FLGS
| CUT_OPT_FIELDS_FLGS
)))
199 bb_error_msg_and_die("expected a list of bytes, characters, or fields");
201 if (opt
& CUT_OPT_DELIM_FLGS
) {
202 if (ltok
[0] && ltok
[1]) { /* more than 1 char? */
203 bb_error_msg_and_die("the delimiter must be a single character");
208 /* non-field (char or byte) cutting has some special handling */
209 if (!(opt
& CUT_OPT_FIELDS_FLGS
)) {
210 static const char _op_on_field
[] ALIGN1
= " only when operating on fields";
212 if (opt
& CUT_OPT_SUPPRESS_FLGS
) {
214 ("suppressing non-delimited lines makes sense%s",
219 ("a delimiter may be specified%s", _op_on_field
);
224 * parse list and put values into startpos and endpos.
225 * valid list formats: N, N-, N-M, -M
226 * more than one list can be separated by commas
232 /* take apart the lists, one by one (they are separated with commas) */
233 while ((ltok
= strsep(&sopt
, ",")) != NULL
) {
235 /* it's actually legal to pass an empty list */
239 /* get the start pos */
240 ntok
= strsep(<ok
, "-");
244 s
= xatoi_positive(ntok
);
245 /* account for the fact that arrays are zero based, while
246 * the user expects the first char on the line to be char #1 */
251 /* get the end pos */
254 } else if (!ltok
[0]) {
257 e
= xatoi_positive(ltok
);
258 /* if the user specified and end position of 0,
259 * that means "til the end of the line" */
262 e
--; /* again, arrays are zero based, lines are 1 based */
267 /* add the new list */
268 cut_lists
= xrealloc_vector(cut_lists
, 4, nlists
);
269 /* NB: startpos is always >= 0,
270 * while endpos may be = NON_RANGE (-1) */
271 cut_lists
[nlists
].startpos
= s
;
272 cut_lists
[nlists
].endpos
= e
;
276 /* make sure we got some cut positions out of all that */
278 bb_error_msg_and_die("missing list of positions");
280 /* now that the lists are parsed, we need to sort them to make life
281 * easier on us when it comes time to print the chars / fields / lines
283 qsort(cut_lists
, nlists
, sizeof(cut_lists
[0]), cmpfunc
);
287 int retval
= EXIT_SUCCESS
;
290 *--argv
= (char *)"-";
293 FILE *file
= fopen_or_warn_stdin(*argv
);
295 retval
= EXIT_FAILURE
;
298 cut_file(file
, delim
, cut_lists
, nlists
);
299 fclose_if_not_stdin(file
);
302 if (ENABLE_FEATURE_CLEAN_UP
)
304 fflush_stdout_and_exit(retval
);