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 //config: bool "cut (6.7 kb)"
15 //config: cut is used to print selected parts of lines from
16 //config: each file to stdout.
18 //config:config FEATURE_CUT_REGEX
19 //config: bool "cut -F"
21 //config: depends on CUT
23 //config: Allow regex based delimiters.
25 //applet:IF_CUT(APPLET_NOEXEC(cut, cut, BB_DIR_USR_BIN, BB_SUID_DROP, cut))
27 //kbuild:lib-$(CONFIG_CUT) += cut.o
29 //usage:#define cut_trivial_usage
30 //usage: "[OPTIONS] [FILE]..."
31 //usage:#define cut_full_usage "\n\n"
32 //usage: "Print selected fields from FILEs to stdout\n"
33 //usage: "\n -b LIST Output only bytes from LIST"
34 //usage: "\n -c LIST Output only characters from LIST"
35 //usage: "\n -d SEP Field delimiter for input (default -f TAB, -F run of whitespace)"
36 //usage: "\n -O SEP Field delimeter for output (default = -d for -f, one space for -F)"
37 //usage: "\n -D Don't sort/collate sections or match -fF lines without delimeter"
38 //usage: "\n -f LIST Print only these fields (-d is single char)"
39 //usage: IF_FEATURE_CUT_REGEX(
40 //usage: "\n -F LIST Print only these fields (-d is regex)"
42 //usage: "\n -s Output only lines containing delimiter"
43 //usage: "\n -n Ignored"
44 //(manpage:-n with -b: don't split multibyte characters)
46 //usage:#define cut_example_usage
47 //usage: "$ echo \"Hello world\" | cut -f 1 -d ' '\n"
49 //usage: "$ echo \"Hello world\" | cut -f 2 -d ' '\n"
54 #if ENABLE_FEATURE_CUT_REGEX
58 typedef struct { int rm_eo
, rm_so
; } regmatch_t
;
59 #define xregcomp(x, ...) *(x) = 0
60 #define regexec(...) 0
63 /* This is a NOEXEC applet. Be very careful! */
67 #define OPT_STR "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
68 #define CUT_OPT_BYTE_FLGS (1 << 0)
69 #define CUT_OPT_CHAR_FLGS (1 << 1)
70 #define CUT_OPT_FIELDS_FLGS (1 << 2)
71 #define CUT_OPT_DELIM_FLGS (1 << 3)
72 #define CUT_OPT_ODELIM_FLGS (1 << 4)
73 #define CUT_OPT_SUPPRESS_FLGS (1 << 5)
74 #define CUT_OPT_NOSORT_FLGS (1 << 6)
75 #define CUT_OPT_REGEX_FLGS ((1 << 7) * ENABLE_FEATURE_CUT_REGEX)
82 static int cmpfunc(const void *a
, const void *b
)
84 return (((struct cut_list
*) a
)->startpos
-
85 ((struct cut_list
*) b
)->startpos
);
88 static void cut_file(FILE *file
, const char *delim
, const char *odelim
,
89 const struct cut_list
*cut_lists
, unsigned nlists
)
92 unsigned linenum
= 0; /* keep these zero-based to be consistent */
94 int spos
, shoe
= option_mask32
& CUT_OPT_REGEX_FLGS
;
96 if (shoe
) xregcomp(®
, delim
, REG_EXTENDED
);
98 /* go through every line in the file */
99 while ((line
= xmalloc_fgetline(file
)) != NULL
) {
101 /* set up a list so we can keep track of what's been printed */
102 int linelen
= strlen(line
);
103 char *printed
= xzalloc(linelen
+ 1);
104 char *orig_line
= line
;
107 /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */
108 if (option_mask32
& (CUT_OPT_CHAR_FLGS
| CUT_OPT_BYTE_FLGS
)) {
109 /* print the chars specified in each cut list */
110 for (; cl_pos
< nlists
; cl_pos
++) {
111 for (spos
= cut_lists
[cl_pos
].startpos
; spos
< linelen
;) {
112 if (!printed
[spos
]) {
116 if (++spos
> cut_lists
[cl_pos
].endpos
) {
121 } else if (*delim
== '\n') { /* cut by lines */
122 spos
= cut_lists
[cl_pos
].startpos
;
124 /* get out if we have no more lists to process or if the lines
125 * are lower than what we're interested in */
126 if (((int)linenum
< spos
) || (cl_pos
>= nlists
))
129 /* if the line we're looking for is lower than the one we were
130 * passed, it means we displayed it already, so move on */
131 while (spos
< (int)linenum
) {
133 /* go to the next list if we're at the end of this one */
134 if (spos
> cut_lists
[cl_pos
].endpos
) {
136 /* get out if there's no more lists to process */
137 if (cl_pos
>= nlists
)
139 spos
= cut_lists
[cl_pos
].startpos
;
140 /* get out if the current line is lower than the one
141 * we just became interested in */
142 if ((int)linenum
< spos
)
147 /* If we made it here, it means we've found the line we're
148 * looking for, so print it */
151 } else { /* cut by fields */
152 unsigned uu
= 0, start
= 0, end
= 0, out
= 0;
155 /* Loop through bytes, finding next delimiter */
157 /* End of current range? */
158 if (end
== linelen
|| dcount
> cut_lists
[cl_pos
].endpos
) {
159 if (++cl_pos
>= nlists
) break;
160 if (option_mask32
& CUT_OPT_NOSORT_FLGS
)
161 start
= dcount
= uu
= 0;
164 /* End of current line? */
166 /* If we've seen no delimiters, check -s */
167 if (!cl_pos
&& !dcount
&& !shoe
) {
168 if (option_mask32
& CUT_OPT_SUPPRESS_FLGS
)
170 } else if (dcount
< cut_lists
[cl_pos
].startpos
)
174 /* Find next delimiter */
176 regmatch_t rr
= {-1, -1};
178 if (!regexec(®
, line
+uu
, 1, &rr
, REG_NOTBOL
|REG_NOTEOL
)) {
185 } else if (line
[end
= uu
++] != *delim
)
188 /* Got delimiter. Loop if not yet within range. */
189 if (dcount
++ < cut_lists
[cl_pos
].startpos
) {
194 if (end
!= start
|| !shoe
)
195 printf("%s%.*s", out
++ ? odelim
: "", end
-start
, line
+ start
);
201 /* if we printed anything, finish with newline */
210 int cut_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
211 int cut_main(int argc UNUSED_PARAM
, char **argv
)
213 /* growable array holding a series of lists */
214 struct cut_list
*cut_lists
= NULL
;
215 unsigned nlists
= 0; /* number of elements in above list */
217 const char *delim
= NULL
;
218 const char *odelim
= NULL
;
221 #define ARG "bcf"IF_FEATURE_CUT_REGEX("F")
222 opt
= getopt32(argv
, "^"
223 OPT_STR
// = "b:c:f:d:O:sD"IF_FEATURE_CUT_REGEX("F:")"n"
224 "\0" "b--"ARG
":c--"ARG
":f--"ARG
IF_FEATURE_CUT_REGEX("F--"ARG
),
225 &sopt
, &sopt
, &sopt
, &delim
, &odelim
IF_FEATURE_CUT_REGEX(, &sopt
)
227 if (!delim
|| !*delim
)
228 delim
= (opt
& CUT_OPT_REGEX_FLGS
) ? "[[:space:]]+" : "\t";
229 if (!odelim
) odelim
= (opt
& CUT_OPT_REGEX_FLGS
) ? " " : delim
;
233 if (!(opt
& (CUT_OPT_BYTE_FLGS
| CUT_OPT_CHAR_FLGS
| CUT_OPT_FIELDS_FLGS
| CUT_OPT_REGEX_FLGS
)))
234 bb_simple_error_msg_and_die("expected a list of bytes, characters, or fields");
236 /* non-field (char or byte) cutting has some special handling */
237 if (!(opt
& (CUT_OPT_FIELDS_FLGS
|CUT_OPT_REGEX_FLGS
))) {
238 static const char _op_on_field
[] ALIGN1
= " only when operating on fields";
240 if (opt
& CUT_OPT_SUPPRESS_FLGS
) {
242 ("suppressing non-delimited lines makes sense%s", _op_on_field
);
244 if (opt
& CUT_OPT_DELIM_FLGS
) {
246 ("a delimiter may be specified%s", _op_on_field
);
251 * parse list and put values into startpos and endpos.
252 * valid list formats: N, N-, N-M, -M
253 * more than one list can be separated by commas
259 /* take apart the lists, one by one (they are separated with commas) */
260 while ((ltok
= strsep(&sopt
, ",")) != NULL
) {
262 /* it's actually legal to pass an empty list */
266 /* get the start pos */
267 ntok
= strsep(<ok
, "-");
271 s
= xatoi_positive(ntok
);
272 /* account for the fact that arrays are zero based, while
273 * the user expects the first char on the line to be char #1 */
278 /* get the end pos */
281 } else if (!ltok
[0]) {
284 e
= xatoi_positive(ltok
);
285 /* if the user specified and end position of 0,
286 * that means "til the end of the line" */
290 bb_error_msg_and_die("%d<%d", e
, s
);
291 e
--; /* again, arrays are zero based, lines are 1 based */
294 /* add the new list */
295 cut_lists
= xrealloc_vector(cut_lists
, 4, nlists
);
296 /* NB: startpos is always >= 0 */
297 cut_lists
[nlists
].startpos
= s
;
298 cut_lists
[nlists
].endpos
= e
;
302 /* make sure we got some cut positions out of all that */
304 bb_simple_error_msg_and_die("missing list of positions");
306 /* now that the lists are parsed, we need to sort them to make life
307 * easier on us when it comes time to print the chars / fields / lines
309 if (!(opt
& CUT_OPT_NOSORT_FLGS
))
310 qsort(cut_lists
, nlists
, sizeof(cut_lists
[0]), cmpfunc
);
314 exitcode_t retval
= EXIT_SUCCESS
;
317 *--argv
= (char *)"-";
320 FILE *file
= fopen_or_warn_stdin(*argv
);
322 retval
= EXIT_FAILURE
;
325 cut_file(file
, delim
, odelim
, cut_lists
, nlists
);
326 fclose_if_not_stdin(file
);
329 if (ENABLE_FEATURE_CLEAN_UP
)
331 fflush_stdout_and_exit(retval
);