1 | /* vi: set sw=4 ts=4: */
|
---|
2 | /*
|
---|
3 | * sed.c - very minimalist version of sed
|
---|
4 | *
|
---|
5 | * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
|
---|
6 | * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
|
---|
7 | * Copyright (C) 2002 Matt Kraai
|
---|
8 | * Copyright (C) 2003 by Glenn McGrath <bug1@iinet.net.au>
|
---|
9 | * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
|
---|
10 | *
|
---|
11 | * MAINTAINER: Rob Landley <rob@landley.net>
|
---|
12 | *
|
---|
13 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
|
---|
14 | */
|
---|
15 |
|
---|
16 | /* Code overview.
|
---|
17 |
|
---|
18 | Files are laid out to avoid unnecessary function declarations. So for
|
---|
19 | example, every function add_cmd calls occurs before add_cmd in this file.
|
---|
20 |
|
---|
21 | add_cmd() is called on each line of sed command text (from a file or from
|
---|
22 | the command line). It calls get_address() and parse_cmd_args(). The
|
---|
23 | resulting sed_cmd_t structures are appended to a linked list
|
---|
24 | (bbg.sed_cmd_head/bbg.sed_cmd_tail).
|
---|
25 |
|
---|
26 | add_input_file() adds a FILE * to the list of input files. We need to
|
---|
27 | know all input sources ahead of time to find the last line for the $ match.
|
---|
28 |
|
---|
29 | process_files() does actual sedding, reading data lines from each input FILE *
|
---|
30 | (which could be stdin) and applying the sed command list (sed_cmd_head) to
|
---|
31 | each of the resulting lines.
|
---|
32 |
|
---|
33 | sed_main() is where external code calls into this, with a command line.
|
---|
34 | */
|
---|
35 |
|
---|
36 |
|
---|
37 | /*
|
---|
38 | Supported features and commands in this version of sed:
|
---|
39 |
|
---|
40 | - comments ('#')
|
---|
41 | - address matching: num|/matchstr/[,num|/matchstr/|$]command
|
---|
42 | - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
|
---|
43 | - edit commands: (a)ppend, (i)nsert, (c)hange
|
---|
44 | - file commands: (r)ead
|
---|
45 | - backreferences in substitution expressions (\0, \1, \2...\9)
|
---|
46 | - grouped commands: {cmd1;cmd2}
|
---|
47 | - transliteration (y/source-chars/dest-chars/)
|
---|
48 | - pattern space hold space storing / swapping (g, h, x)
|
---|
49 | - labels / branching (: label, b, t, T)
|
---|
50 |
|
---|
51 | (Note: Specifying an address (range) to match is *optional*; commands
|
---|
52 | default to the whole pattern space if no specific address match was
|
---|
53 | requested.)
|
---|
54 |
|
---|
55 | Todo:
|
---|
56 | - Create a wrapper around regex to make libc's regex conform with sed
|
---|
57 |
|
---|
58 | Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
|
---|
59 | */
|
---|
60 |
|
---|
61 | #include "busybox.h"
|
---|
62 | #include "xregex.h"
|
---|
63 |
|
---|
64 | /* Each sed command turns into one of these structures. */
|
---|
65 | typedef struct sed_cmd_s {
|
---|
66 | /* Ordered by alignment requirements: currently 36 bytes on x86 */
|
---|
67 |
|
---|
68 | /* address storage */
|
---|
69 | regex_t *beg_match; /* sed -e '/match/cmd' */
|
---|
70 | regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */
|
---|
71 | regex_t *sub_match; /* For 's/sub_match/string/' */
|
---|
72 | int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
|
---|
73 | int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
|
---|
74 |
|
---|
75 | FILE *file; /* File (sw) command writes to, -1 for none. */
|
---|
76 | char *string; /* Data string for (saicytb) commands. */
|
---|
77 |
|
---|
78 | unsigned short which_match; /* (s) Which match to replace (0 for all) */
|
---|
79 |
|
---|
80 | /* Bitfields (gcc won't group them if we don't) */
|
---|
81 | unsigned int invert:1; /* the '!' after the address */
|
---|
82 | unsigned int in_match:1; /* Next line also included in match? */
|
---|
83 | unsigned int no_newline:1; /* Last line written by (sw) had no '\n' */
|
---|
84 | unsigned int sub_p:1; /* (s) print option */
|
---|
85 |
|
---|
86 | /* GENERAL FIELDS */
|
---|
87 | char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
|
---|
88 | struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
|
---|
89 | } sed_cmd_t;
|
---|
90 |
|
---|
91 | static const char *const semicolon_whitespace = "; \n\r\t\v";
|
---|
92 |
|
---|
93 | struct sed_globals
|
---|
94 | {
|
---|
95 | /* options */
|
---|
96 | int be_quiet, in_place, regex_type;
|
---|
97 | FILE *nonstdout;
|
---|
98 | char *outname, *hold_space;
|
---|
99 |
|
---|
100 | /* List of input files */
|
---|
101 | int input_file_count,current_input_file;
|
---|
102 | FILE **input_file_list;
|
---|
103 |
|
---|
104 | regmatch_t regmatch[10];
|
---|
105 | regex_t *previous_regex_ptr;
|
---|
106 |
|
---|
107 | /* linked list of sed commands */
|
---|
108 | sed_cmd_t sed_cmd_head, *sed_cmd_tail;
|
---|
109 |
|
---|
110 | /* Linked list of append lines */
|
---|
111 | llist_t *append_head;
|
---|
112 |
|
---|
113 | char *add_cmd_line;
|
---|
114 |
|
---|
115 | struct pipeline {
|
---|
116 | char *buf; /* Space to hold string */
|
---|
117 | int idx; /* Space used */
|
---|
118 | int len; /* Space allocated */
|
---|
119 | } pipeline;
|
---|
120 | } bbg;
|
---|
121 |
|
---|
122 |
|
---|
123 | void sed_free_and_close_stuff(void);
|
---|
124 | #if ENABLE_FEATURE_CLEAN_UP
|
---|
125 | void sed_free_and_close_stuff(void)
|
---|
126 | {
|
---|
127 | sed_cmd_t *sed_cmd = bbg.sed_cmd_head.next;
|
---|
128 |
|
---|
129 | llist_free(bbg.append_head, free);
|
---|
130 |
|
---|
131 | while (sed_cmd) {
|
---|
132 | sed_cmd_t *sed_cmd_next = sed_cmd->next;
|
---|
133 |
|
---|
134 | if(sed_cmd->file)
|
---|
135 | bb_xprint_and_close_file(sed_cmd->file);
|
---|
136 |
|
---|
137 | if (sed_cmd->beg_match) {
|
---|
138 | regfree(sed_cmd->beg_match);
|
---|
139 | free(sed_cmd->beg_match);
|
---|
140 | }
|
---|
141 | if (sed_cmd->end_match) {
|
---|
142 | regfree(sed_cmd->end_match);
|
---|
143 | free(sed_cmd->end_match);
|
---|
144 | }
|
---|
145 | if (sed_cmd->sub_match) {
|
---|
146 | regfree(sed_cmd->sub_match);
|
---|
147 | free(sed_cmd->sub_match);
|
---|
148 | }
|
---|
149 | free(sed_cmd->string);
|
---|
150 | free(sed_cmd);
|
---|
151 | sed_cmd = sed_cmd_next;
|
---|
152 | }
|
---|
153 |
|
---|
154 | if(bbg.hold_space) free(bbg.hold_space);
|
---|
155 |
|
---|
156 | while(bbg.current_input_file<bbg.input_file_count)
|
---|
157 | fclose(bbg.input_file_list[bbg.current_input_file++]);
|
---|
158 | }
|
---|
159 | #endif
|
---|
160 |
|
---|
161 | /* If something bad happens during -i operation, delete temp file */
|
---|
162 |
|
---|
163 | static void cleanup_outname(void)
|
---|
164 | {
|
---|
165 | if(bbg.outname) unlink(bbg.outname);
|
---|
166 | }
|
---|
167 |
|
---|
168 | /* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */
|
---|
169 |
|
---|
170 | static void parse_escapes(char *dest, char *string, int len, char from, char to)
|
---|
171 | {
|
---|
172 | int i=0;
|
---|
173 |
|
---|
174 | while(i<len) {
|
---|
175 | if(string[i] == '\\') {
|
---|
176 | if(!to || string[i+1] == from) {
|
---|
177 | *(dest++) = to ? to : string[i+1];
|
---|
178 | i+=2;
|
---|
179 | continue;
|
---|
180 | } else *(dest++)=string[i++];
|
---|
181 | }
|
---|
182 | *(dest++) = string[i++];
|
---|
183 | }
|
---|
184 | *dest=0;
|
---|
185 | }
|
---|
186 |
|
---|
187 | static char *copy_parsing_escapes(char *string, int len)
|
---|
188 | {
|
---|
189 | char *dest=xmalloc(len+1);
|
---|
190 |
|
---|
191 | parse_escapes(dest,string,len,'n','\n');
|
---|
192 | return dest;
|
---|
193 | }
|
---|
194 |
|
---|
195 |
|
---|
196 | /*
|
---|
197 | * index_of_next_unescaped_regexp_delim - walks left to right through a string
|
---|
198 | * beginning at a specified index and returns the index of the next regular
|
---|
199 | * expression delimiter (typically a forward * slash ('/')) not preceded by
|
---|
200 | * a backslash ('\'). A negative delimiter disables square bracket checking.
|
---|
201 | */
|
---|
202 | static int index_of_next_unescaped_regexp_delim(int delimiter, char *str)
|
---|
203 | {
|
---|
204 | int bracket = -1;
|
---|
205 | int escaped = 0;
|
---|
206 | int idx = 0;
|
---|
207 | char ch;
|
---|
208 |
|
---|
209 | if (delimiter < 0) {
|
---|
210 | bracket--;
|
---|
211 | delimiter *= -1;
|
---|
212 | }
|
---|
213 |
|
---|
214 | for (; (ch = str[idx]); idx++) {
|
---|
215 | if (bracket >= 0) {
|
---|
216 | if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2
|
---|
217 | && str[idx - 1] == '^')))
|
---|
218 | bracket = -1;
|
---|
219 | } else if (escaped)
|
---|
220 | escaped = 0;
|
---|
221 | else if (ch == '\\')
|
---|
222 | escaped = 1;
|
---|
223 | else if (bracket == -1 && ch == '[')
|
---|
224 | bracket = idx;
|
---|
225 | else if (ch == delimiter)
|
---|
226 | return idx;
|
---|
227 | }
|
---|
228 |
|
---|
229 | /* if we make it to here, we've hit the end of the string */
|
---|
230 | bb_error_msg_and_die("unmatched '%c'",delimiter);
|
---|
231 | }
|
---|
232 |
|
---|
233 | /*
|
---|
234 | * Returns the index of the third delimiter
|
---|
235 | */
|
---|
236 | static int parse_regex_delim(char *cmdstr, char **match, char **replace)
|
---|
237 | {
|
---|
238 | char *cmdstr_ptr = cmdstr;
|
---|
239 | char delimiter;
|
---|
240 | int idx = 0;
|
---|
241 |
|
---|
242 | /* verify that the 's' or 'y' is followed by something. That something
|
---|
243 | * (typically a 'slash') is now our regexp delimiter... */
|
---|
244 | if (*cmdstr == '\0')
|
---|
245 | bb_error_msg_and_die("bad format in substitution expression");
|
---|
246 | delimiter = *(cmdstr_ptr++);
|
---|
247 |
|
---|
248 | /* save the match string */
|
---|
249 | idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
|
---|
250 | *match = copy_parsing_escapes(cmdstr_ptr, idx);
|
---|
251 |
|
---|
252 | /* save the replacement string */
|
---|
253 | cmdstr_ptr += idx + 1;
|
---|
254 | idx = index_of_next_unescaped_regexp_delim(-delimiter, cmdstr_ptr);
|
---|
255 | *replace = copy_parsing_escapes(cmdstr_ptr, idx);
|
---|
256 |
|
---|
257 | return ((cmdstr_ptr - cmdstr) + idx);
|
---|
258 | }
|
---|
259 |
|
---|
260 | /*
|
---|
261 | * returns the index in the string just past where the address ends.
|
---|
262 | */
|
---|
263 | static int get_address(char *my_str, int *linenum, regex_t ** regex)
|
---|
264 | {
|
---|
265 | char *pos = my_str;
|
---|
266 |
|
---|
267 | if (isdigit(*my_str)) {
|
---|
268 | *linenum = strtol(my_str, &pos, 10);
|
---|
269 | /* endstr shouldnt ever equal NULL */
|
---|
270 | } else if (*my_str == '$') {
|
---|
271 | *linenum = -1;
|
---|
272 | pos++;
|
---|
273 | } else if (*my_str == '/' || *my_str == '\\') {
|
---|
274 | int next;
|
---|
275 | char delimiter;
|
---|
276 | char *temp;
|
---|
277 |
|
---|
278 | if (*my_str == '\\') delimiter = *(++pos);
|
---|
279 | else delimiter = '/';
|
---|
280 | next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
|
---|
281 | temp = copy_parsing_escapes(pos,next);
|
---|
282 | *regex = (regex_t *) xmalloc(sizeof(regex_t));
|
---|
283 | xregcomp(*regex, temp, bbg.regex_type|REG_NEWLINE);
|
---|
284 | free(temp);
|
---|
285 | /* Move position to next character after last delimiter */
|
---|
286 | pos += (next+1);
|
---|
287 | }
|
---|
288 | return pos - my_str;
|
---|
289 | }
|
---|
290 |
|
---|
291 | /* Grab a filename. Whitespace at start is skipped, then goes to EOL. */
|
---|
292 | static int parse_file_cmd(sed_cmd_t *sed_cmd, char *filecmdstr, char **retval)
|
---|
293 | {
|
---|
294 | int start = 0, idx, hack=0;
|
---|
295 |
|
---|
296 | /* Skip whitespace, then grab filename to end of line */
|
---|
297 | while (isspace(filecmdstr[start])) start++;
|
---|
298 | idx=start;
|
---|
299 | while(filecmdstr[idx] && filecmdstr[idx]!='\n') idx++;
|
---|
300 | /* If lines glued together, put backslash back. */
|
---|
301 | if(filecmdstr[idx]=='\n') hack=1;
|
---|
302 | if(idx==start) bb_error_msg_and_die("Empty filename");
|
---|
303 | *retval = bb_xstrndup(filecmdstr+start, idx-start+hack+1);
|
---|
304 | if(hack) *(idx+*retval)='\\';
|
---|
305 |
|
---|
306 | return idx;
|
---|
307 | }
|
---|
308 |
|
---|
309 | static int parse_subst_cmd(sed_cmd_t *sed_cmd, char *substr)
|
---|
310 | {
|
---|
311 | int cflags = bbg.regex_type;
|
---|
312 | char *match;
|
---|
313 | int idx = 0;
|
---|
314 |
|
---|
315 | /*
|
---|
316 | * A substitution command should look something like this:
|
---|
317 | * s/match/replace/ #gIpw
|
---|
318 | * || | |||
|
---|
319 | * mandatory optional
|
---|
320 | */
|
---|
321 | idx = parse_regex_delim(substr, &match, &sed_cmd->string);
|
---|
322 |
|
---|
323 | /* determine the number of back references in the match string */
|
---|
324 | /* Note: we compute this here rather than in the do_subst_command()
|
---|
325 | * function to save processor time, at the expense of a little more memory
|
---|
326 | * (4 bits) per sed_cmd */
|
---|
327 |
|
---|
328 | /* process the flags */
|
---|
329 |
|
---|
330 | sed_cmd->which_match=1;
|
---|
331 | while (substr[++idx]) {
|
---|
332 | /* Parse match number */
|
---|
333 | if(isdigit(substr[idx])) {
|
---|
334 | if(match[0]!='^') {
|
---|
335 | /* Match 0 treated as all, multiple matches we take the last one. */
|
---|
336 | char *pos=substr+idx;
|
---|
337 | sed_cmd->which_match=(unsigned short)strtol(substr+idx,&pos,10);
|
---|
338 | idx=pos-substr;
|
---|
339 | }
|
---|
340 | continue;
|
---|
341 | }
|
---|
342 | /* Skip spaces */
|
---|
343 | if(isspace(substr[idx])) continue;
|
---|
344 |
|
---|
345 | switch (substr[idx]) {
|
---|
346 | /* Replace all occurrences */
|
---|
347 | case 'g':
|
---|
348 | if (match[0] != '^') sed_cmd->which_match = 0;
|
---|
349 | break;
|
---|
350 | /* Print pattern space */
|
---|
351 | case 'p':
|
---|
352 | sed_cmd->sub_p = 1;
|
---|
353 | break;
|
---|
354 | /* Write to file */
|
---|
355 | case 'w':
|
---|
356 | {
|
---|
357 | char *temp;
|
---|
358 | idx+=parse_file_cmd(sed_cmd,substr+idx,&temp);
|
---|
359 |
|
---|
360 | break;
|
---|
361 | }
|
---|
362 | /* Ignore case (gnu exension) */
|
---|
363 | case 'I':
|
---|
364 | cflags |= REG_ICASE;
|
---|
365 | break;
|
---|
366 | /* Comment */
|
---|
367 | case '#':
|
---|
368 | while(substr[++idx]);
|
---|
369 | /* Fall through */
|
---|
370 | /* End of command */
|
---|
371 | case ';':
|
---|
372 | case '}':
|
---|
373 | goto out;
|
---|
374 | default:
|
---|
375 | bb_error_msg_and_die("bad option in substitution expression");
|
---|
376 | }
|
---|
377 | }
|
---|
378 | out:
|
---|
379 | /* compile the match string into a regex */
|
---|
380 | if (*match != '\0') {
|
---|
381 | /* If match is empty, we use last regex used at runtime */
|
---|
382 | sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t));
|
---|
383 | xregcomp(sed_cmd->sub_match, match, cflags);
|
---|
384 | }
|
---|
385 | free(match);
|
---|
386 |
|
---|
387 | return idx;
|
---|
388 | }
|
---|
389 |
|
---|
390 | /*
|
---|
391 | * Process the commands arguments
|
---|
392 | */
|
---|
393 | static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr)
|
---|
394 | {
|
---|
395 | /* handle (s)ubstitution command */
|
---|
396 | if (sed_cmd->cmd == 's') cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
|
---|
397 | /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
|
---|
398 | else if (strchr("aic", sed_cmd->cmd)) {
|
---|
399 | if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c')
|
---|
400 | bb_error_msg_and_die
|
---|
401 | ("only a beginning address can be specified for edit commands");
|
---|
402 | for(;;) {
|
---|
403 | if(*cmdstr=='\n' || *cmdstr=='\\') {
|
---|
404 | cmdstr++;
|
---|
405 | break;
|
---|
406 | } else if(isspace(*cmdstr)) cmdstr++;
|
---|
407 | else break;
|
---|
408 | }
|
---|
409 | sed_cmd->string = bb_xstrdup(cmdstr);
|
---|
410 | parse_escapes(sed_cmd->string,sed_cmd->string,strlen(cmdstr),0,0);
|
---|
411 | cmdstr += strlen(cmdstr);
|
---|
412 | /* handle file cmds: (r)ead */
|
---|
413 | } else if(strchr("rw", sed_cmd->cmd)) {
|
---|
414 | if (sed_cmd->end_line || sed_cmd->end_match)
|
---|
415 | bb_error_msg_and_die("Command only uses one address");
|
---|
416 | cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string);
|
---|
417 | if(sed_cmd->cmd=='w')
|
---|
418 | sed_cmd->file=bb_xfopen(sed_cmd->string,"w");
|
---|
419 | /* handle branch commands */
|
---|
420 | } else if (strchr(":btT", sed_cmd->cmd)) {
|
---|
421 | int length;
|
---|
422 |
|
---|
423 | while(isspace(*cmdstr)) cmdstr++;
|
---|
424 | length = strcspn(cmdstr, semicolon_whitespace);
|
---|
425 | if (length) {
|
---|
426 | sed_cmd->string = bb_xstrndup(cmdstr, length);
|
---|
427 | cmdstr += length;
|
---|
428 | }
|
---|
429 | }
|
---|
430 | /* translation command */
|
---|
431 | else if (sed_cmd->cmd == 'y') {
|
---|
432 | char *match, *replace;
|
---|
433 | int i=cmdstr[0];
|
---|
434 |
|
---|
435 | cmdstr+=parse_regex_delim(cmdstr, &match, &replace)+1;
|
---|
436 | /* \n already parsed, but \delimiter needs unescaping. */
|
---|
437 | parse_escapes(match,match,strlen(match),i,i);
|
---|
438 | parse_escapes(replace,replace,strlen(replace),i,i);
|
---|
439 |
|
---|
440 | sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
|
---|
441 | for (i = 0; match[i] && replace[i]; i++) {
|
---|
442 | sed_cmd->string[i * 2] = match[i];
|
---|
443 | sed_cmd->string[(i * 2) + 1] = replace[i];
|
---|
444 | }
|
---|
445 | free(match);
|
---|
446 | free(replace);
|
---|
447 | }
|
---|
448 | /* if it wasnt a single-letter command that takes no arguments
|
---|
449 | * then it must be an invalid command.
|
---|
450 | */
|
---|
451 | else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) {
|
---|
452 | bb_error_msg_and_die("Unsupported command %c", sed_cmd->cmd);
|
---|
453 | }
|
---|
454 |
|
---|
455 | /* give back whatever's left over */
|
---|
456 | return (cmdstr);
|
---|
457 | }
|
---|
458 |
|
---|
459 |
|
---|
460 | /* Parse address+command sets, skipping comment lines. */
|
---|
461 |
|
---|
462 | static void add_cmd(char *cmdstr)
|
---|
463 | {
|
---|
464 | sed_cmd_t *sed_cmd;
|
---|
465 | int temp;
|
---|
466 |
|
---|
467 | /* Append this line to any unfinished line from last time. */
|
---|
468 | if (bbg.add_cmd_line) {
|
---|
469 | cmdstr = bb_xasprintf("%s\n%s", bbg.add_cmd_line, cmdstr);
|
---|
470 | free(bbg.add_cmd_line);
|
---|
471 | bbg.add_cmd_line = cmdstr;
|
---|
472 | }
|
---|
473 |
|
---|
474 | /* If this line ends with backslash, request next line. */
|
---|
475 | temp=strlen(cmdstr);
|
---|
476 | if(temp && cmdstr[temp-1]=='\\') {
|
---|
477 | if (!bbg.add_cmd_line) bbg.add_cmd_line = bb_xstrdup(cmdstr);
|
---|
478 | bbg.add_cmd_line[temp-1] = 0;
|
---|
479 | return;
|
---|
480 | }
|
---|
481 |
|
---|
482 | /* Loop parsing all commands in this line. */
|
---|
483 | while(*cmdstr) {
|
---|
484 | /* Skip leading whitespace and semicolons */
|
---|
485 | cmdstr += strspn(cmdstr, semicolon_whitespace);
|
---|
486 |
|
---|
487 | /* If no more commands, exit. */
|
---|
488 | if(!*cmdstr) break;
|
---|
489 |
|
---|
490 | /* if this is a comment, jump past it and keep going */
|
---|
491 | if (*cmdstr == '#') {
|
---|
492 | /* "#n" is the same as using -n on the command line */
|
---|
493 | if (cmdstr[1] == 'n') bbg.be_quiet++;
|
---|
494 | if(!(cmdstr=strpbrk(cmdstr, "\n\r"))) break;
|
---|
495 | continue;
|
---|
496 | }
|
---|
497 |
|
---|
498 | /* parse the command
|
---|
499 | * format is: [addr][,addr][!]cmd
|
---|
500 | * |----||-----||-|
|
---|
501 | * part1 part2 part3
|
---|
502 | */
|
---|
503 |
|
---|
504 | sed_cmd = xzalloc(sizeof(sed_cmd_t));
|
---|
505 |
|
---|
506 | /* first part (if present) is an address: either a '$', a number or a /regex/ */
|
---|
507 | cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
|
---|
508 |
|
---|
509 | /* second part (if present) will begin with a comma */
|
---|
510 | if (*cmdstr == ',') {
|
---|
511 | int idx;
|
---|
512 |
|
---|
513 | cmdstr++;
|
---|
514 | idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
|
---|
515 | if (!idx) bb_error_msg_and_die("no address after comma\n");
|
---|
516 | cmdstr += idx;
|
---|
517 | }
|
---|
518 |
|
---|
519 | /* skip whitespace before the command */
|
---|
520 | while (isspace(*cmdstr)) cmdstr++;
|
---|
521 |
|
---|
522 | /* Check for inversion flag */
|
---|
523 | if (*cmdstr == '!') {
|
---|
524 | sed_cmd->invert = 1;
|
---|
525 | cmdstr++;
|
---|
526 |
|
---|
527 | /* skip whitespace before the command */
|
---|
528 | while (isspace(*cmdstr)) cmdstr++;
|
---|
529 | }
|
---|
530 |
|
---|
531 | /* last part (mandatory) will be a command */
|
---|
532 | if (!*cmdstr) bb_error_msg_and_die("missing command");
|
---|
533 | sed_cmd->cmd = *(cmdstr++);
|
---|
534 | cmdstr = parse_cmd_args(sed_cmd, cmdstr);
|
---|
535 |
|
---|
536 | /* Add the command to the command array */
|
---|
537 | bbg.sed_cmd_tail->next = sed_cmd;
|
---|
538 | bbg.sed_cmd_tail = bbg.sed_cmd_tail->next;
|
---|
539 | }
|
---|
540 |
|
---|
541 | /* If we glued multiple lines together, free the memory. */
|
---|
542 | free(bbg.add_cmd_line);
|
---|
543 | bbg.add_cmd_line = NULL;
|
---|
544 | }
|
---|
545 |
|
---|
546 | /* Append to a string, reallocating memory as necessary. */
|
---|
547 |
|
---|
548 | #define PIPE_GROW 64
|
---|
549 |
|
---|
550 | static void pipe_putc(char c)
|
---|
551 | {
|
---|
552 | if(bbg.pipeline.idx==bbg.pipeline.len) {
|
---|
553 | bbg.pipeline.buf = xrealloc(bbg.pipeline.buf,
|
---|
554 | bbg.pipeline.len + PIPE_GROW);
|
---|
555 | bbg.pipeline.len+=PIPE_GROW;
|
---|
556 | }
|
---|
557 | bbg.pipeline.buf[bbg.pipeline.idx++] = c;
|
---|
558 | }
|
---|
559 |
|
---|
560 | static void do_subst_w_backrefs(char *line, char *replace)
|
---|
561 | {
|
---|
562 | int i,j;
|
---|
563 |
|
---|
564 | /* go through the replacement string */
|
---|
565 | for (i = 0; replace[i]; i++) {
|
---|
566 | /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */
|
---|
567 | if (replace[i] == '\\' && replace[i+1]>='0' && replace[i+1]<='9') {
|
---|
568 | int backref=replace[++i]-'0';
|
---|
569 |
|
---|
570 | /* print out the text held in bbg.regmatch[backref] */
|
---|
571 | if(bbg.regmatch[backref].rm_so != -1)
|
---|
572 | for (j = bbg.regmatch[backref].rm_so;
|
---|
573 | j < bbg.regmatch[backref].rm_eo; j++) pipe_putc(line[j]);
|
---|
574 | }
|
---|
575 |
|
---|
576 | /* if we find a backslash escaped character, print the character */
|
---|
577 | else if (replace[i] == '\\') pipe_putc(replace[++i]);
|
---|
578 |
|
---|
579 | /* if we find an unescaped '&' print out the whole matched text. */
|
---|
580 | else if (replace[i] == '&')
|
---|
581 | for (j = bbg.regmatch[0].rm_so; j < bbg.regmatch[0].rm_eo; j++)
|
---|
582 | pipe_putc(line[j]);
|
---|
583 | /* Otherwise just output the character. */
|
---|
584 | else pipe_putc(replace[i]);
|
---|
585 | }
|
---|
586 | }
|
---|
587 |
|
---|
588 | static int do_subst_command(sed_cmd_t *sed_cmd, char **line)
|
---|
589 | {
|
---|
590 | char *oldline = *line;
|
---|
591 | int altered = 0;
|
---|
592 | int match_count=0;
|
---|
593 | regex_t *current_regex;
|
---|
594 |
|
---|
595 | /* Handle empty regex. */
|
---|
596 | if (sed_cmd->sub_match == NULL) {
|
---|
597 | current_regex = bbg.previous_regex_ptr;
|
---|
598 | if(!current_regex)
|
---|
599 | bb_error_msg_and_die("No previous regexp.");
|
---|
600 | } else bbg.previous_regex_ptr = current_regex = sed_cmd->sub_match;
|
---|
601 |
|
---|
602 | /* Find the first match */
|
---|
603 | if(REG_NOMATCH==regexec(current_regex, oldline, 10, bbg.regmatch, 0))
|
---|
604 | return 0;
|
---|
605 |
|
---|
606 | /* Initialize temporary output buffer. */
|
---|
607 | bbg.pipeline.buf=xmalloc(PIPE_GROW);
|
---|
608 | bbg.pipeline.len=PIPE_GROW;
|
---|
609 | bbg.pipeline.idx=0;
|
---|
610 |
|
---|
611 | /* Now loop through, substituting for matches */
|
---|
612 | do {
|
---|
613 | int i;
|
---|
614 |
|
---|
615 | /* Work around bug in glibc regexec, demonstrated by:
|
---|
616 | echo " a.b" | busybox sed 's [^ .]* x g'
|
---|
617 | The match_count check is so not to break
|
---|
618 | echo "hi" | busybox sed 's/^/!/g' */
|
---|
619 | if(!bbg.regmatch[0].rm_so && !bbg.regmatch[0].rm_eo && match_count) {
|
---|
620 | pipe_putc(*(oldline++));
|
---|
621 | continue;
|
---|
622 | }
|
---|
623 |
|
---|
624 | match_count++;
|
---|
625 |
|
---|
626 | /* If we aren't interested in this match, output old line to
|
---|
627 | end of match and continue */
|
---|
628 | if(sed_cmd->which_match && sed_cmd->which_match!=match_count) {
|
---|
629 | for(i=0;i<bbg.regmatch[0].rm_eo;i++)
|
---|
630 | pipe_putc(*(oldline++));
|
---|
631 | continue;
|
---|
632 | }
|
---|
633 |
|
---|
634 | /* print everything before the match */
|
---|
635 | for (i = 0; i < bbg.regmatch[0].rm_so; i++) pipe_putc(oldline[i]);
|
---|
636 |
|
---|
637 | /* then print the substitution string */
|
---|
638 | do_subst_w_backrefs(oldline, sed_cmd->string);
|
---|
639 |
|
---|
640 | /* advance past the match */
|
---|
641 | oldline += bbg.regmatch[0].rm_eo;
|
---|
642 | /* flag that something has changed */
|
---|
643 | altered++;
|
---|
644 |
|
---|
645 | /* if we're not doing this globally, get out now */
|
---|
646 | if (sed_cmd->which_match) break;
|
---|
647 | } while (*oldline && (regexec(current_regex, oldline, 10, bbg.regmatch, 0) != REG_NOMATCH));
|
---|
648 |
|
---|
649 | /* Copy rest of string into output pipeline */
|
---|
650 |
|
---|
651 | while(*oldline) pipe_putc(*(oldline++));
|
---|
652 | pipe_putc(0);
|
---|
653 |
|
---|
654 | free(*line);
|
---|
655 | *line = bbg.pipeline.buf;
|
---|
656 | return altered;
|
---|
657 | }
|
---|
658 |
|
---|
659 | /* Set command pointer to point to this label. (Does not handle null label.) */
|
---|
660 | static sed_cmd_t *branch_to(char *label)
|
---|
661 | {
|
---|
662 | sed_cmd_t *sed_cmd;
|
---|
663 |
|
---|
664 | for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
|
---|
665 | if ((sed_cmd->cmd == ':') && (sed_cmd->string) && (strcmp(sed_cmd->string, label) == 0)) {
|
---|
666 | return (sed_cmd);
|
---|
667 | }
|
---|
668 | }
|
---|
669 | bb_error_msg_and_die("Can't find label for jump to `%s'", label);
|
---|
670 | }
|
---|
671 |
|
---|
672 | static void append(char *s)
|
---|
673 | {
|
---|
674 | llist_add_to_end(&bbg.append_head, bb_xstrdup(s));
|
---|
675 | }
|
---|
676 |
|
---|
677 | static void flush_append(void)
|
---|
678 | {
|
---|
679 | char *data;
|
---|
680 |
|
---|
681 | /* Output appended lines. */
|
---|
682 | while((data = (char *)llist_pop(&bbg.append_head))) {
|
---|
683 | fprintf(bbg.nonstdout,"%s\n",data);
|
---|
684 | free(data);
|
---|
685 | }
|
---|
686 | }
|
---|
687 |
|
---|
688 | static void add_input_file(FILE *file)
|
---|
689 | {
|
---|
690 | bbg.input_file_list=xrealloc(bbg.input_file_list,
|
---|
691 | (bbg.input_file_count + 1) * sizeof(FILE *));
|
---|
692 | bbg.input_file_list[bbg.input_file_count++] = file;
|
---|
693 | }
|
---|
694 |
|
---|
695 | /* Get next line of input from bbg.input_file_list, flushing append buffer and
|
---|
696 | * noting if we ran out of files without a newline on the last line we read.
|
---|
697 | */
|
---|
698 | static char *get_next_line(int *no_newline)
|
---|
699 | {
|
---|
700 | char *temp=NULL;
|
---|
701 | int len;
|
---|
702 |
|
---|
703 | flush_append();
|
---|
704 | while (bbg.current_input_file<bbg.input_file_count) {
|
---|
705 | temp = bb_get_chunk_from_file(bbg.input_file_list[bbg.current_input_file],&len);
|
---|
706 | if (temp) {
|
---|
707 | *no_newline = !(len && temp[len-1]=='\n');
|
---|
708 | if (!*no_newline) temp[len-1] = 0;
|
---|
709 | break;
|
---|
710 | // Close this file and advance to next one
|
---|
711 | } else fclose(bbg.input_file_list[bbg.current_input_file++]);
|
---|
712 | }
|
---|
713 |
|
---|
714 | return temp;
|
---|
715 | }
|
---|
716 |
|
---|
717 | /* Output line of text. missing_newline means the last line output did not
|
---|
718 | end with a newline. no_newline means this line does not end with a
|
---|
719 | newline. */
|
---|
720 |
|
---|
721 | static int puts_maybe_newline(char *s, FILE *file, int missing_newline, int no_newline)
|
---|
722 | {
|
---|
723 | if(missing_newline) fputc('\n',file);
|
---|
724 | fputs(s,file);
|
---|
725 | if(!no_newline) fputc('\n',file);
|
---|
726 |
|
---|
727 | if(ferror(file)) {
|
---|
728 | bb_default_error_retval = 4; /* It's what gnu sed exits with... */
|
---|
729 | bb_error_msg_and_die(bb_msg_write_error);
|
---|
730 | }
|
---|
731 |
|
---|
732 | return no_newline;
|
---|
733 | }
|
---|
734 |
|
---|
735 | #define sed_puts(s,n) missing_newline=puts_maybe_newline(s,bbg.nonstdout,missing_newline,n)
|
---|
736 |
|
---|
737 | /* Process all the lines in all the files */
|
---|
738 |
|
---|
739 | static void process_files(void)
|
---|
740 | {
|
---|
741 | char *pattern_space, *next_line;
|
---|
742 | int linenum = 0, missing_newline=0;
|
---|
743 | int no_newline,next_no_newline=0;
|
---|
744 |
|
---|
745 | /* Prime the pump */
|
---|
746 | next_line = get_next_line(&next_no_newline);
|
---|
747 |
|
---|
748 | /* go through every line in each file */
|
---|
749 | for(;;) {
|
---|
750 | sed_cmd_t *sed_cmd;
|
---|
751 | int substituted=0;
|
---|
752 |
|
---|
753 | /* Advance to next line. Stop if out of lines. */
|
---|
754 | if(!(pattern_space=next_line)) break;
|
---|
755 | no_newline=next_no_newline;
|
---|
756 |
|
---|
757 | /* Read one line in advance so we can act on the last line,
|
---|
758 | * the '$' address */
|
---|
759 | next_line = get_next_line(&next_no_newline);
|
---|
760 | linenum++;
|
---|
761 | restart:
|
---|
762 | /* for every line, go through all the commands */
|
---|
763 | for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next)
|
---|
764 | {
|
---|
765 | int old_matched, matched;
|
---|
766 |
|
---|
767 | old_matched = sed_cmd->in_match;
|
---|
768 |
|
---|
769 | /* Determine if this command matches this line: */
|
---|
770 |
|
---|
771 | /* Are we continuing a previous multi-line match? */
|
---|
772 |
|
---|
773 | sed_cmd->in_match = sed_cmd->in_match
|
---|
774 |
|
---|
775 | /* Or is no range necessary? */
|
---|
776 | || (!sed_cmd->beg_line && !sed_cmd->end_line
|
---|
777 | && !sed_cmd->beg_match && !sed_cmd->end_match)
|
---|
778 |
|
---|
779 | /* Or did we match the start of a numerical range? */
|
---|
780 | || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum))
|
---|
781 |
|
---|
782 | /* Or does this line match our begin address regex? */
|
---|
783 | || (sed_cmd->beg_match &&
|
---|
784 | !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0))
|
---|
785 |
|
---|
786 | /* Or did we match last line of input? */
|
---|
787 | || (sed_cmd->beg_line == -1 && next_line == NULL);
|
---|
788 |
|
---|
789 | /* Snapshot the value */
|
---|
790 |
|
---|
791 | matched = sed_cmd->in_match;
|
---|
792 |
|
---|
793 | /* Is this line the end of the current match? */
|
---|
794 |
|
---|
795 | if(matched) {
|
---|
796 | sed_cmd->in_match = !(
|
---|
797 | /* has the ending line come, or is this a single address command? */
|
---|
798 | (sed_cmd->end_line ?
|
---|
799 | sed_cmd->end_line==-1 ?
|
---|
800 | !next_line
|
---|
801 | : sed_cmd->end_line<=linenum
|
---|
802 | : !sed_cmd->end_match)
|
---|
803 | /* or does this line matches our last address regex */
|
---|
804 | || (sed_cmd->end_match && old_matched && (regexec(sed_cmd->end_match, pattern_space, 0, NULL, 0) == 0))
|
---|
805 | );
|
---|
806 | }
|
---|
807 |
|
---|
808 | /* Skip blocks of commands we didn't match. */
|
---|
809 | if (sed_cmd->cmd == '{') {
|
---|
810 | if(sed_cmd->invert ? matched : !matched)
|
---|
811 | while(sed_cmd && sed_cmd->cmd!='}') sed_cmd=sed_cmd->next;
|
---|
812 | if(!sed_cmd) bb_error_msg_and_die("Unterminated {");
|
---|
813 | continue;
|
---|
814 | }
|
---|
815 |
|
---|
816 | /* Okay, so did this line match? */
|
---|
817 | if (sed_cmd->invert ? !matched : matched) {
|
---|
818 | /* Update last used regex in case a blank substitute BRE is found */
|
---|
819 | if (sed_cmd->beg_match) {
|
---|
820 | bbg.previous_regex_ptr = sed_cmd->beg_match;
|
---|
821 | }
|
---|
822 |
|
---|
823 | /* actual sedding */
|
---|
824 | switch (sed_cmd->cmd) {
|
---|
825 |
|
---|
826 | /* Print line number */
|
---|
827 | case '=':
|
---|
828 | fprintf(bbg.nonstdout,"%d\n", linenum);
|
---|
829 | break;
|
---|
830 |
|
---|
831 | /* Write the current pattern space up to the first newline */
|
---|
832 | case 'P':
|
---|
833 | {
|
---|
834 | char *tmp = strchr(pattern_space, '\n');
|
---|
835 |
|
---|
836 | if (tmp) {
|
---|
837 | *tmp = '\0';
|
---|
838 | sed_puts(pattern_space,1);
|
---|
839 | *tmp = '\n';
|
---|
840 | break;
|
---|
841 | }
|
---|
842 | /* Fall Through */
|
---|
843 | }
|
---|
844 |
|
---|
845 | /* Write the current pattern space to output */
|
---|
846 | case 'p':
|
---|
847 | sed_puts(pattern_space,no_newline);
|
---|
848 | break;
|
---|
849 | /* Delete up through first newline */
|
---|
850 | case 'D':
|
---|
851 | {
|
---|
852 | char *tmp = strchr(pattern_space,'\n');
|
---|
853 |
|
---|
854 | if(tmp) {
|
---|
855 | tmp=bb_xstrdup(tmp+1);
|
---|
856 | free(pattern_space);
|
---|
857 | pattern_space=tmp;
|
---|
858 | goto restart;
|
---|
859 | }
|
---|
860 | }
|
---|
861 | /* discard this line. */
|
---|
862 | case 'd':
|
---|
863 | goto discard_line;
|
---|
864 |
|
---|
865 | /* Substitute with regex */
|
---|
866 | case 's':
|
---|
867 | if(do_subst_command(sed_cmd, &pattern_space)) {
|
---|
868 | substituted|=1;
|
---|
869 |
|
---|
870 | /* handle p option */
|
---|
871 | if(sed_cmd->sub_p)
|
---|
872 | sed_puts(pattern_space,no_newline);
|
---|
873 | /* handle w option */
|
---|
874 | if(sed_cmd->file)
|
---|
875 | sed_cmd->no_newline=puts_maybe_newline(pattern_space, sed_cmd->file, sed_cmd->no_newline, no_newline);
|
---|
876 |
|
---|
877 | }
|
---|
878 | break;
|
---|
879 |
|
---|
880 | /* Append line to linked list to be printed later */
|
---|
881 | case 'a':
|
---|
882 | {
|
---|
883 | append(sed_cmd->string);
|
---|
884 | break;
|
---|
885 | }
|
---|
886 |
|
---|
887 | /* Insert text before this line */
|
---|
888 | case 'i':
|
---|
889 | sed_puts(sed_cmd->string,1);
|
---|
890 | break;
|
---|
891 |
|
---|
892 | /* Cut and paste text (replace) */
|
---|
893 | case 'c':
|
---|
894 | /* Only triggers on last line of a matching range. */
|
---|
895 | if (!sed_cmd->in_match) sed_puts(sed_cmd->string,0);
|
---|
896 | goto discard_line;
|
---|
897 |
|
---|
898 | /* Read file, append contents to output */
|
---|
899 | case 'r':
|
---|
900 | {
|
---|
901 | FILE *rfile;
|
---|
902 |
|
---|
903 | rfile = fopen(sed_cmd->string, "r");
|
---|
904 | if (rfile) {
|
---|
905 | char *line;
|
---|
906 |
|
---|
907 | while ((line = bb_get_chomped_line_from_file(rfile))
|
---|
908 | != NULL)
|
---|
909 | append(line);
|
---|
910 | bb_xprint_and_close_file(rfile);
|
---|
911 | }
|
---|
912 |
|
---|
913 | break;
|
---|
914 | }
|
---|
915 |
|
---|
916 | /* Write pattern space to file. */
|
---|
917 | case 'w':
|
---|
918 | sed_cmd->no_newline=puts_maybe_newline(pattern_space,sed_cmd->file, sed_cmd->no_newline,no_newline);
|
---|
919 | break;
|
---|
920 |
|
---|
921 | /* Read next line from input */
|
---|
922 | case 'n':
|
---|
923 | if (!bbg.be_quiet)
|
---|
924 | sed_puts(pattern_space,no_newline);
|
---|
925 | if (next_line) {
|
---|
926 | free(pattern_space);
|
---|
927 | pattern_space = next_line;
|
---|
928 | no_newline=next_no_newline;
|
---|
929 | next_line = get_next_line(&next_no_newline);
|
---|
930 | linenum++;
|
---|
931 | break;
|
---|
932 | }
|
---|
933 | /* fall through */
|
---|
934 |
|
---|
935 | /* Quit. End of script, end of input. */
|
---|
936 | case 'q':
|
---|
937 | /* Exit the outer while loop */
|
---|
938 | free(next_line);
|
---|
939 | next_line = NULL;
|
---|
940 | goto discard_commands;
|
---|
941 |
|
---|
942 | /* Append the next line to the current line */
|
---|
943 | case 'N':
|
---|
944 | {
|
---|
945 | /* If no next line, jump to end of script and exit. */
|
---|
946 | if (next_line == NULL) {
|
---|
947 | /* Jump to end of script and exit */
|
---|
948 | free(next_line);
|
---|
949 | next_line = NULL;
|
---|
950 | goto discard_line;
|
---|
951 | /* append next_line, read new next_line. */
|
---|
952 | } else {
|
---|
953 | int len=strlen(pattern_space);
|
---|
954 |
|
---|
955 | pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
|
---|
956 | pattern_space[len]='\n';
|
---|
957 | strcpy(pattern_space+len+1, next_line);
|
---|
958 | no_newline=next_no_newline;
|
---|
959 | next_line = get_next_line(&next_no_newline);
|
---|
960 | linenum++;
|
---|
961 | }
|
---|
962 | break;
|
---|
963 | }
|
---|
964 |
|
---|
965 | /* Test/branch if substitution occurred */
|
---|
966 | case 't':
|
---|
967 | if(!substituted) break;
|
---|
968 | substituted=0;
|
---|
969 | /* Fall through */
|
---|
970 | /* Test/branch if substitution didn't occur */
|
---|
971 | case 'T':
|
---|
972 | if (substituted) break;
|
---|
973 | /* Fall through */
|
---|
974 | /* Branch to label */
|
---|
975 | case 'b':
|
---|
976 | if (!sed_cmd->string) goto discard_commands;
|
---|
977 | else sed_cmd = branch_to(sed_cmd->string);
|
---|
978 | break;
|
---|
979 | /* Transliterate characters */
|
---|
980 | case 'y':
|
---|
981 | {
|
---|
982 | int i;
|
---|
983 |
|
---|
984 | for (i = 0; pattern_space[i]; i++) {
|
---|
985 | int j;
|
---|
986 |
|
---|
987 | for (j = 0; sed_cmd->string[j]; j += 2) {
|
---|
988 | if (pattern_space[i] == sed_cmd->string[j]) {
|
---|
989 | pattern_space[i] = sed_cmd->string[j + 1];
|
---|
990 | break;
|
---|
991 | }
|
---|
992 | }
|
---|
993 | }
|
---|
994 |
|
---|
995 | break;
|
---|
996 | }
|
---|
997 | case 'g': /* Replace pattern space with hold space */
|
---|
998 | free(pattern_space);
|
---|
999 | pattern_space = bb_xstrdup(bbg.hold_space ? bbg.hold_space : "");
|
---|
1000 | break;
|
---|
1001 | case 'G': /* Append newline and hold space to pattern space */
|
---|
1002 | {
|
---|
1003 | int pattern_space_size = 2;
|
---|
1004 | int hold_space_size = 0;
|
---|
1005 |
|
---|
1006 | if (pattern_space)
|
---|
1007 | pattern_space_size += strlen(pattern_space);
|
---|
1008 | if (bbg.hold_space)
|
---|
1009 | hold_space_size = strlen(bbg.hold_space);
|
---|
1010 | pattern_space = xrealloc(pattern_space,
|
---|
1011 | pattern_space_size + hold_space_size);
|
---|
1012 | if (pattern_space_size == 2) pattern_space[0]=0;
|
---|
1013 | strcat(pattern_space, "\n");
|
---|
1014 | if (bbg.hold_space)
|
---|
1015 | strcat(pattern_space, bbg.hold_space);
|
---|
1016 | no_newline=0;
|
---|
1017 |
|
---|
1018 | break;
|
---|
1019 | }
|
---|
1020 | case 'h': /* Replace hold space with pattern space */
|
---|
1021 | free(bbg.hold_space);
|
---|
1022 | bbg.hold_space = bb_xstrdup(pattern_space);
|
---|
1023 | break;
|
---|
1024 | case 'H': /* Append newline and pattern space to hold space */
|
---|
1025 | {
|
---|
1026 | int hold_space_size = 2;
|
---|
1027 | int pattern_space_size = 0;
|
---|
1028 |
|
---|
1029 | if (bbg.hold_space)
|
---|
1030 | hold_space_size += strlen(bbg.hold_space);
|
---|
1031 | if (pattern_space)
|
---|
1032 | pattern_space_size = strlen(pattern_space);
|
---|
1033 | bbg.hold_space = xrealloc(bbg.hold_space,
|
---|
1034 | hold_space_size + pattern_space_size);
|
---|
1035 |
|
---|
1036 | if (hold_space_size == 2) *bbg.hold_space=0;
|
---|
1037 | strcat(bbg.hold_space, "\n");
|
---|
1038 | if (pattern_space) strcat(bbg.hold_space, pattern_space);
|
---|
1039 |
|
---|
1040 | break;
|
---|
1041 | }
|
---|
1042 | case 'x': /* Exchange hold and pattern space */
|
---|
1043 | {
|
---|
1044 | char *tmp = pattern_space;
|
---|
1045 | pattern_space = bbg.hold_space ? : xzalloc(1);
|
---|
1046 | no_newline=0;
|
---|
1047 | bbg.hold_space = tmp;
|
---|
1048 | break;
|
---|
1049 | }
|
---|
1050 | }
|
---|
1051 | }
|
---|
1052 | }
|
---|
1053 |
|
---|
1054 | /*
|
---|
1055 | * exit point from sedding...
|
---|
1056 | */
|
---|
1057 | discard_commands:
|
---|
1058 | /* we will print the line unless we were told to be quiet ('-n')
|
---|
1059 | or if the line was suppressed (ala 'd'elete) */
|
---|
1060 | if (!bbg.be_quiet) sed_puts(pattern_space,no_newline);
|
---|
1061 |
|
---|
1062 | /* Delete and such jump here. */
|
---|
1063 | discard_line:
|
---|
1064 | flush_append();
|
---|
1065 | free(pattern_space);
|
---|
1066 | }
|
---|
1067 | }
|
---|
1068 |
|
---|
1069 | /* It is possible to have a command line argument with embedded
|
---|
1070 | newlines. This counts as multiple command lines. */
|
---|
1071 |
|
---|
1072 | static void add_cmd_block(char *cmdstr)
|
---|
1073 | {
|
---|
1074 | int go=1;
|
---|
1075 | char *temp=bb_xstrdup(cmdstr),*temp2=temp;
|
---|
1076 |
|
---|
1077 | while(go) {
|
---|
1078 | int len=strcspn(temp2,"\n");
|
---|
1079 | if(!temp2[len]) go=0;
|
---|
1080 | else temp2[len]=0;
|
---|
1081 | add_cmd(temp2);
|
---|
1082 | temp2+=len+1;
|
---|
1083 | }
|
---|
1084 | free(temp);
|
---|
1085 | }
|
---|
1086 |
|
---|
1087 | int sed_main(int argc, char **argv)
|
---|
1088 | {
|
---|
1089 | int status = EXIT_SUCCESS, opt, getpat = 1;
|
---|
1090 |
|
---|
1091 | bbg.sed_cmd_tail=&bbg.sed_cmd_head;
|
---|
1092 |
|
---|
1093 | /* destroy command strings on exit */
|
---|
1094 | if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);
|
---|
1095 |
|
---|
1096 | /* Lie to autoconf when it starts asking stupid questions. */
|
---|
1097 | if(argc==2 && !strcmp(argv[1],"--version")) {
|
---|
1098 | printf("This is not GNU sed version 4.0\n");
|
---|
1099 | exit(0);
|
---|
1100 | }
|
---|
1101 |
|
---|
1102 | /* do normal option parsing */
|
---|
1103 | while ((opt = getopt(argc, argv, "irne:f:")) > 0) {
|
---|
1104 | switch (opt) {
|
---|
1105 | case 'i':
|
---|
1106 | bbg.in_place++;
|
---|
1107 | atexit(cleanup_outname);
|
---|
1108 | break;
|
---|
1109 | case 'r':
|
---|
1110 | bbg.regex_type|=REG_EXTENDED;
|
---|
1111 | break;
|
---|
1112 | case 'n':
|
---|
1113 | bbg.be_quiet++;
|
---|
1114 | break;
|
---|
1115 | case 'e':
|
---|
1116 | add_cmd_block(optarg);
|
---|
1117 | getpat=0;
|
---|
1118 | break;
|
---|
1119 | case 'f':
|
---|
1120 | {
|
---|
1121 | FILE *cmdfile;
|
---|
1122 | char *line;
|
---|
1123 |
|
---|
1124 | cmdfile = bb_xfopen(optarg, "r");
|
---|
1125 |
|
---|
1126 | while ((line = bb_get_chomped_line_from_file(cmdfile)) != NULL) {
|
---|
1127 | add_cmd(line);
|
---|
1128 | getpat=0;
|
---|
1129 | free(line);
|
---|
1130 | }
|
---|
1131 | bb_xprint_and_close_file(cmdfile);
|
---|
1132 |
|
---|
1133 | break;
|
---|
1134 | }
|
---|
1135 | default:
|
---|
1136 | bb_show_usage();
|
---|
1137 | }
|
---|
1138 | }
|
---|
1139 |
|
---|
1140 | /* if we didn't get a pattern from -e or -f, use argv[optind] */
|
---|
1141 | if(getpat) {
|
---|
1142 | if (argv[optind] == NULL)
|
---|
1143 | bb_show_usage();
|
---|
1144 | else
|
---|
1145 | add_cmd_block(argv[optind++]);
|
---|
1146 | }
|
---|
1147 | /* Flush any unfinished commands. */
|
---|
1148 | add_cmd("");
|
---|
1149 |
|
---|
1150 | /* By default, we write to stdout */
|
---|
1151 | bbg.nonstdout=stdout;
|
---|
1152 |
|
---|
1153 | /* argv[(optind)..(argc-1)] should be names of file to process. If no
|
---|
1154 | * files were specified or '-' was specified, take input from stdin.
|
---|
1155 | * Otherwise, we process all the files specified. */
|
---|
1156 | if (argv[optind] == NULL) {
|
---|
1157 | if(bbg.in_place) bb_error_msg_and_die(bb_msg_requires_arg, "-i");
|
---|
1158 | add_input_file(stdin);
|
---|
1159 | process_files();
|
---|
1160 | } else {
|
---|
1161 | int i;
|
---|
1162 | FILE *file;
|
---|
1163 |
|
---|
1164 | for (i = optind; i < argc; i++) {
|
---|
1165 | if(!strcmp(argv[i], "-") && !bbg.in_place) {
|
---|
1166 | add_input_file(stdin);
|
---|
1167 | process_files();
|
---|
1168 | } else {
|
---|
1169 | file = bb_wfopen(argv[i], "r");
|
---|
1170 | if (file) {
|
---|
1171 | if(bbg.in_place) {
|
---|
1172 | struct stat statbuf;
|
---|
1173 | int nonstdoutfd;
|
---|
1174 |
|
---|
1175 | bbg.outname=bb_xstrndup(argv[i],strlen(argv[i])+6);
|
---|
1176 | strcat(bbg.outname,"XXXXXX");
|
---|
1177 | if(-1==(nonstdoutfd=mkstemp(bbg.outname)))
|
---|
1178 | bb_error_msg_and_die("no temp file");
|
---|
1179 | bbg.nonstdout=fdopen(nonstdoutfd,"w");
|
---|
1180 |
|
---|
1181 | /* Set permissions of output file */
|
---|
1182 |
|
---|
1183 | fstat(fileno(file),&statbuf);
|
---|
1184 | fchmod(nonstdoutfd,statbuf.st_mode);
|
---|
1185 | add_input_file(file);
|
---|
1186 | process_files();
|
---|
1187 | fclose(bbg.nonstdout);
|
---|
1188 |
|
---|
1189 | bbg.nonstdout=stdout;
|
---|
1190 | unlink(argv[i]);
|
---|
1191 | rename(bbg.outname,argv[i]);
|
---|
1192 | free(bbg.outname);
|
---|
1193 | bbg.outname=0;
|
---|
1194 | } else add_input_file(file);
|
---|
1195 | } else {
|
---|
1196 | status = EXIT_FAILURE;
|
---|
1197 | }
|
---|
1198 | }
|
---|
1199 | }
|
---|
1200 | if(bbg.input_file_count>bbg.current_input_file) process_files();
|
---|
1201 | }
|
---|
1202 |
|
---|
1203 | return status;
|
---|
1204 | }
|
---|