5 |
* Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley |
* Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley |
6 |
* Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org> |
* Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org> |
7 |
* Copyright (C) 2002 Matt Kraai |
* Copyright (C) 2002 Matt Kraai |
8 |
* Copyright (C) 2003 by Glenn McGrath <bug1@iinet.net.au> |
* Copyright (C) 2003 by Glenn McGrath |
9 |
* Copyright (C) 2003,2004 by Rob Landley <rob@landley.net> |
* Copyright (C) 2003,2004 by Rob Landley <rob@landley.net> |
10 |
* |
* |
11 |
* MAINTAINER: Rob Landley <rob@landley.net> |
* MAINTAINER: Rob Landley <rob@landley.net> |
21 |
add_cmd() is called on each line of sed command text (from a file or from |
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 |
the command line). It calls get_address() and parse_cmd_args(). The |
23 |
resulting sed_cmd_t structures are appended to a linked list |
resulting sed_cmd_t structures are appended to a linked list |
24 |
(bbg.sed_cmd_head/bbg.sed_cmd_tail). |
(G.sed_cmd_head/G.sed_cmd_tail). |
25 |
|
|
26 |
add_input_file() adds a FILE * to the list of input files. We need to |
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. |
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 * |
process_files() does actual sedding, reading data lines from each input FILE * |
58 |
Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html |
Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html |
59 |
*/ |
*/ |
60 |
|
|
61 |
#include "busybox.h" |
#include "libbb.h" |
62 |
#include "xregex.h" |
#include "xregex.h" |
63 |
|
|
64 |
/* Each sed command turns into one of these structures. */ |
/* Each sed command turns into one of these structures. */ |
65 |
typedef struct sed_cmd_s { |
typedef struct sed_cmd_s { |
66 |
/* Ordered by alignment requirements: currently 36 bytes on x86 */ |
/* Ordered by alignment requirements: currently 36 bytes on x86 */ |
67 |
|
struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */ |
68 |
|
|
69 |
/* address storage */ |
/* address storage */ |
70 |
regex_t *beg_match; /* sed -e '/match/cmd' */ |
regex_t *beg_match; /* sed -e '/match/cmd' */ |
73 |
int beg_line; /* 'sed 1p' 0 == apply commands to all lines */ |
int beg_line; /* 'sed 1p' 0 == apply commands to all lines */ |
74 |
int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */ |
int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */ |
75 |
|
|
76 |
FILE *file; /* File (sw) command writes to, -1 for none. */ |
FILE *sw_file; /* File (sw) command writes to, -1 for none. */ |
77 |
char *string; /* Data string for (saicytb) commands. */ |
char *string; /* Data string for (saicytb) commands. */ |
78 |
|
|
79 |
unsigned short which_match; /* (s) Which match to replace (0 for all) */ |
unsigned which_match; /* (s) Which match to replace (0 for all) */ |
80 |
|
|
81 |
/* Bitfields (gcc won't group them if we don't) */ |
/* Bitfields (gcc won't group them if we don't) */ |
82 |
unsigned int invert:1; /* the '!' after the address */ |
unsigned invert:1; /* the '!' after the address */ |
83 |
unsigned int in_match:1; /* Next line also included in match? */ |
unsigned in_match:1; /* Next line also included in match? */ |
84 |
unsigned int sub_p:1; /* (s) print option */ |
unsigned sub_p:1; /* (s) print option */ |
85 |
|
|
86 |
int last_char; /* Last line written by (sw) had no '\n' */ |
char sw_last_char; /* Last line written by (sw) had no '\n' */ |
87 |
|
|
88 |
/* GENERAL FIELDS */ |
/* GENERAL FIELDS */ |
89 |
char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */ |
char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */ |
|
struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */ |
|
90 |
} sed_cmd_t; |
} sed_cmd_t; |
91 |
|
|
92 |
static const char *const semicolon_whitespace = "; \n\r\t\v"; |
static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v"; |
93 |
|
|
94 |
struct sed_globals { |
struct globals { |
95 |
/* options */ |
/* options */ |
96 |
int be_quiet, regex_type; |
int be_quiet, regex_type; |
97 |
FILE *nonstdout; |
FILE *nonstdout; |
117 |
int idx; /* Space used */ |
int idx; /* Space used */ |
118 |
int len; /* Space allocated */ |
int len; /* Space allocated */ |
119 |
} pipeline; |
} pipeline; |
120 |
} bbg; |
}; |
121 |
|
#define G (*(struct globals*)&bb_common_bufsiz1) |
122 |
|
void BUG_sed_globals_too_big(void); |
123 |
|
#define INIT_G() do { \ |
124 |
|
if (sizeof(struct globals) > COMMON_BUFSIZE) \ |
125 |
|
BUG_sed_globals_too_big(); \ |
126 |
|
G.sed_cmd_tail = &G.sed_cmd_head; \ |
127 |
|
} while (0) |
128 |
|
|
129 |
|
|
130 |
#if ENABLE_FEATURE_CLEAN_UP |
#if ENABLE_FEATURE_CLEAN_UP |
131 |
static void sed_free_and_close_stuff(void) |
static void sed_free_and_close_stuff(void) |
132 |
{ |
{ |
133 |
sed_cmd_t *sed_cmd = bbg.sed_cmd_head.next; |
sed_cmd_t *sed_cmd = G.sed_cmd_head.next; |
134 |
|
|
135 |
llist_free(bbg.append_head, free); |
llist_free(G.append_head, free); |
136 |
|
|
137 |
while (sed_cmd) { |
while (sed_cmd) { |
138 |
sed_cmd_t *sed_cmd_next = sed_cmd->next; |
sed_cmd_t *sed_cmd_next = sed_cmd->next; |
139 |
|
|
140 |
if (sed_cmd->file) |
if (sed_cmd->sw_file) |
141 |
xprint_and_close_file(sed_cmd->file); |
xprint_and_close_file(sed_cmd->sw_file); |
142 |
|
|
143 |
if (sed_cmd->beg_match) { |
if (sed_cmd->beg_match) { |
144 |
regfree(sed_cmd->beg_match); |
regfree(sed_cmd->beg_match); |
157 |
sed_cmd = sed_cmd_next; |
sed_cmd = sed_cmd_next; |
158 |
} |
} |
159 |
|
|
160 |
if (bbg.hold_space) free(bbg.hold_space); |
free(G.hold_space); |
161 |
|
|
162 |
while (bbg.current_input_file < bbg.input_file_count) |
while (G.current_input_file < G.input_file_count) |
163 |
fclose(bbg.input_file_list[bbg.current_input_file++]); |
fclose(G.input_file_list[G.current_input_file++]); |
164 |
} |
} |
165 |
#else |
#else |
166 |
void sed_free_and_close_stuff(void); |
void sed_free_and_close_stuff(void); |
170 |
|
|
171 |
static void cleanup_outname(void) |
static void cleanup_outname(void) |
172 |
{ |
{ |
173 |
if (bbg.outname) unlink(bbg.outname); |
if (G.outname) unlink(G.outname); |
174 |
} |
} |
175 |
|
|
176 |
/* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */ |
/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */ |
177 |
|
|
178 |
static void parse_escapes(char *dest, char *string, int len, char from, char to) |
static void parse_escapes(char *dest, const char *string, int len, char from, char to) |
179 |
{ |
{ |
180 |
int i = 0; |
int i = 0; |
181 |
|
|
188 |
} |
} |
189 |
*dest++ = string[i++]; |
*dest++ = string[i++]; |
190 |
} |
} |
191 |
|
/* TODO: is it safe wrt a string with trailing '\\' ? */ |
192 |
*dest++ = string[i++]; |
*dest++ = string[i++]; |
193 |
} |
} |
194 |
*dest = 0; |
*dest = '\0'; |
195 |
} |
} |
196 |
|
|
197 |
static char *copy_parsing_escapes(char *string, int len) |
static char *copy_parsing_escapes(const char *string, int len) |
198 |
{ |
{ |
199 |
char *dest = xmalloc(len + 1); |
char *dest = xmalloc(len + 1); |
200 |
|
|
201 |
parse_escapes(dest, string, len, 'n', '\n'); |
parse_escapes(dest, string, len, 'n', '\n'); |
202 |
|
/* GNU sed also recognizes \t */ |
203 |
|
parse_escapes(dest, dest, strlen(dest), 't', '\t'); |
204 |
return dest; |
return dest; |
205 |
} |
} |
206 |
|
|
208 |
/* |
/* |
209 |
* index_of_next_unescaped_regexp_delim - walks left to right through a string |
* index_of_next_unescaped_regexp_delim - walks left to right through a string |
210 |
* beginning at a specified index and returns the index of the next regular |
* beginning at a specified index and returns the index of the next regular |
211 |
* expression delimiter (typically a forward * slash ('/')) not preceded by |
* expression delimiter (typically a forward slash ('/')) not preceded by |
212 |
* a backslash ('\'). A negative delimiter disables square bracket checking. |
* a backslash ('\'). A negative delimiter disables square bracket checking. |
213 |
*/ |
*/ |
214 |
static int index_of_next_unescaped_regexp_delim(int delimiter, char *str) |
static int index_of_next_unescaped_regexp_delim(int delimiter, const char *str) |
215 |
{ |
{ |
216 |
int bracket = -1; |
int bracket = -1; |
217 |
int escaped = 0; |
int escaped = 0; |
245 |
/* |
/* |
246 |
* Returns the index of the third delimiter |
* Returns the index of the third delimiter |
247 |
*/ |
*/ |
248 |
static int parse_regex_delim(char *cmdstr, char **match, char **replace) |
static int parse_regex_delim(const char *cmdstr, char **match, char **replace) |
249 |
{ |
{ |
250 |
char *cmdstr_ptr = cmdstr; |
const char *cmdstr_ptr = cmdstr; |
251 |
char delimiter; |
char delimiter; |
252 |
int idx = 0; |
int idx = 0; |
253 |
|
|
272 |
/* |
/* |
273 |
* returns the index in the string just past where the address ends. |
* returns the index in the string just past where the address ends. |
274 |
*/ |
*/ |
275 |
static int get_address(char *my_str, int *linenum, regex_t ** regex) |
static int get_address(const char *my_str, int *linenum, regex_t ** regex) |
276 |
{ |
{ |
277 |
char *pos = my_str; |
const char *pos = my_str; |
278 |
|
|
279 |
if (isdigit(*my_str)) { |
if (isdigit(*my_str)) { |
280 |
*linenum = strtol(my_str, &pos, 10); |
*linenum = strtol(my_str, (char**)&pos, 10); |
281 |
/* endstr shouldnt ever equal NULL */ |
/* endstr shouldnt ever equal NULL */ |
282 |
} else if (*my_str == '$') { |
} else if (*my_str == '$') { |
283 |
*linenum = -1; |
*linenum = -1; |
292 |
next = index_of_next_unescaped_regexp_delim(delimiter, ++pos); |
next = index_of_next_unescaped_regexp_delim(delimiter, ++pos); |
293 |
temp = copy_parsing_escapes(pos, next); |
temp = copy_parsing_escapes(pos, next); |
294 |
*regex = xmalloc(sizeof(regex_t)); |
*regex = xmalloc(sizeof(regex_t)); |
295 |
xregcomp(*regex, temp, bbg.regex_type|REG_NEWLINE); |
xregcomp(*regex, temp, G.regex_type|REG_NEWLINE); |
296 |
free(temp); |
free(temp); |
297 |
/* Move position to next character after last delimiter */ |
/* Move position to next character after last delimiter */ |
298 |
pos += (next+1); |
pos += (next+1); |
301 |
} |
} |
302 |
|
|
303 |
/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */ |
/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */ |
304 |
static int parse_file_cmd(sed_cmd_t *sed_cmd, char *filecmdstr, char **retval) |
static int parse_file_cmd(/*sed_cmd_t *sed_cmd,*/ const char *filecmdstr, char **retval) |
305 |
{ |
{ |
306 |
int start = 0, idx, hack = 0; |
int start = 0, idx, hack = 0; |
307 |
|
|
308 |
/* Skip whitespace, then grab filename to end of line */ |
/* Skip whitespace, then grab filename to end of line */ |
309 |
while (isspace(filecmdstr[start])) start++; |
while (isspace(filecmdstr[start])) |
310 |
|
start++; |
311 |
idx = start; |
idx = start; |
312 |
while (filecmdstr[idx] && filecmdstr[idx] != '\n') idx++; |
while (filecmdstr[idx] && filecmdstr[idx] != '\n') |
313 |
|
idx++; |
314 |
|
|
315 |
/* If lines glued together, put backslash back. */ |
/* If lines glued together, put backslash back. */ |
316 |
if (filecmdstr[idx] == '\n') hack = 1; |
if (filecmdstr[idx] == '\n') |
317 |
|
hack = 1; |
318 |
if (idx == start) |
if (idx == start) |
319 |
bb_error_msg_and_die("empty filename"); |
bb_error_msg_and_die("empty filename"); |
320 |
*retval = xstrndup(filecmdstr+start, idx-start+hack+1); |
*retval = xstrndup(filecmdstr+start, idx-start+hack+1); |
321 |
if (hack) (*retval)[idx] = '\\'; |
if (hack) |
322 |
|
(*retval)[idx] = '\\'; |
323 |
|
|
324 |
return idx; |
return idx; |
325 |
} |
} |
326 |
|
|
327 |
static int parse_subst_cmd(sed_cmd_t *sed_cmd, char *substr) |
static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr) |
328 |
{ |
{ |
329 |
int cflags = bbg.regex_type; |
int cflags = G.regex_type; |
330 |
char *match; |
char *match; |
331 |
int idx = 0; |
int idx; |
332 |
|
|
333 |
/* |
/* |
334 |
* A substitution command should look something like this: |
* A substitution command should look something like this: |
351 |
if (isdigit(substr[idx])) { |
if (isdigit(substr[idx])) { |
352 |
if (match[0] != '^') { |
if (match[0] != '^') { |
353 |
/* Match 0 treated as all, multiple matches we take the last one. */ |
/* Match 0 treated as all, multiple matches we take the last one. */ |
354 |
char *pos = substr + idx; |
const char *pos = substr + idx; |
355 |
/* FIXME: error check? */ |
/* FIXME: error check? */ |
356 |
sed_cmd->which_match = (unsigned short)strtol(substr+idx, &pos, 10); |
sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &pos, 10); |
357 |
idx = pos - substr; |
idx = pos - substr; |
358 |
} |
} |
359 |
continue; |
continue; |
364 |
switch (substr[idx]) { |
switch (substr[idx]) { |
365 |
/* Replace all occurrences */ |
/* Replace all occurrences */ |
366 |
case 'g': |
case 'g': |
367 |
if (match[0] != '^') sed_cmd->which_match = 0; |
if (match[0] != '^') |
368 |
|
sed_cmd->which_match = 0; |
369 |
break; |
break; |
370 |
/* Print pattern space */ |
/* Print pattern space */ |
371 |
case 'p': |
case 'p': |
375 |
case 'w': |
case 'w': |
376 |
{ |
{ |
377 |
char *temp; |
char *temp; |
378 |
idx += parse_file_cmd(sed_cmd, substr+idx, &temp); |
idx += parse_file_cmd(/*sed_cmd,*/ substr+idx, &temp); |
|
|
|
379 |
break; |
break; |
380 |
} |
} |
381 |
/* Ignore case (gnu exension) */ |
/* Ignore case (gnu exension) */ |
409 |
/* |
/* |
410 |
* Process the commands arguments |
* Process the commands arguments |
411 |
*/ |
*/ |
412 |
static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr) |
static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const char *cmdstr) |
413 |
{ |
{ |
414 |
/* handle (s)ubstitution command */ |
/* handle (s)ubstitution command */ |
415 |
if (sed_cmd->cmd == 's') |
if (sed_cmd->cmd == 's') |
429 |
break; |
break; |
430 |
} |
} |
431 |
sed_cmd->string = xstrdup(cmdstr); |
sed_cmd->string = xstrdup(cmdstr); |
432 |
parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), 0, 0); |
/* "\anychar" -> "anychar" */ |
433 |
|
parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), '\0', '\0'); |
434 |
cmdstr += strlen(cmdstr); |
cmdstr += strlen(cmdstr); |
435 |
/* handle file cmds: (r)ead */ |
/* handle file cmds: (r)ead */ |
436 |
} else if (strchr("rw", sed_cmd->cmd)) { |
} else if (strchr("rw", sed_cmd->cmd)) { |
437 |
if (sed_cmd->end_line || sed_cmd->end_match) |
if (sed_cmd->end_line || sed_cmd->end_match) |
438 |
bb_error_msg_and_die("command only uses one address"); |
bb_error_msg_and_die("command only uses one address"); |
439 |
cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string); |
cmdstr += parse_file_cmd(/*sed_cmd,*/ cmdstr, &sed_cmd->string); |
440 |
if (sed_cmd->cmd == 'w') |
if (sed_cmd->cmd == 'w') { |
441 |
sed_cmd->file = xfopen(sed_cmd->string, "w"); |
sed_cmd->sw_file = xfopen_for_write(sed_cmd->string); |
442 |
|
sed_cmd->sw_last_char = '\n'; |
443 |
|
} |
444 |
/* handle branch commands */ |
/* handle branch commands */ |
445 |
} else if (strchr(":btT", sed_cmd->cmd)) { |
} else if (strchr(":btT", sed_cmd->cmd)) { |
446 |
int length; |
int length; |
484 |
|
|
485 |
/* Parse address+command sets, skipping comment lines. */ |
/* Parse address+command sets, skipping comment lines. */ |
486 |
|
|
487 |
static void add_cmd(char *cmdstr) |
static void add_cmd(const char *cmdstr) |
488 |
{ |
{ |
489 |
sed_cmd_t *sed_cmd; |
sed_cmd_t *sed_cmd; |
490 |
int temp; |
int temp; |
491 |
|
|
492 |
/* Append this line to any unfinished line from last time. */ |
/* Append this line to any unfinished line from last time. */ |
493 |
if (bbg.add_cmd_line) { |
if (G.add_cmd_line) { |
494 |
cmdstr = xasprintf("%s\n%s", bbg.add_cmd_line, cmdstr); |
char *tp = xasprintf("%s\n%s", G.add_cmd_line, cmdstr); |
495 |
free(bbg.add_cmd_line); |
free(G.add_cmd_line); |
496 |
bbg.add_cmd_line = cmdstr; |
cmdstr = G.add_cmd_line = tp; |
497 |
} |
} |
498 |
|
|
499 |
/* If this line ends with backslash, request next line. */ |
/* If this line ends with backslash, request next line. */ |
500 |
temp = strlen(cmdstr); |
temp = strlen(cmdstr); |
501 |
if (temp && cmdstr[temp-1] == '\\') { |
if (temp && cmdstr[--temp] == '\\') { |
502 |
if (!bbg.add_cmd_line) |
if (!G.add_cmd_line) |
503 |
bbg.add_cmd_line = xstrdup(cmdstr); |
G.add_cmd_line = xstrdup(cmdstr); |
504 |
bbg.add_cmd_line[temp-1] = 0; |
G.add_cmd_line[temp] = '\0'; |
505 |
return; |
return; |
506 |
} |
} |
507 |
|
|
517 |
if (*cmdstr == '#') { |
if (*cmdstr == '#') { |
518 |
/* "#n" is the same as using -n on the command line */ |
/* "#n" is the same as using -n on the command line */ |
519 |
if (cmdstr[1] == 'n') |
if (cmdstr[1] == 'n') |
520 |
bbg.be_quiet++; |
G.be_quiet++; |
521 |
cmdstr = strpbrk(cmdstr, "\n\r"); |
cmdstr = strpbrk(cmdstr, "\n\r"); |
522 |
if (!cmdstr) break; |
if (!cmdstr) break; |
523 |
continue; |
continue; |
564 |
cmdstr = parse_cmd_args(sed_cmd, cmdstr); |
cmdstr = parse_cmd_args(sed_cmd, cmdstr); |
565 |
|
|
566 |
/* Add the command to the command array */ |
/* Add the command to the command array */ |
567 |
bbg.sed_cmd_tail->next = sed_cmd; |
G.sed_cmd_tail->next = sed_cmd; |
568 |
bbg.sed_cmd_tail = bbg.sed_cmd_tail->next; |
G.sed_cmd_tail = G.sed_cmd_tail->next; |
569 |
} |
} |
570 |
|
|
571 |
/* If we glued multiple lines together, free the memory. */ |
/* If we glued multiple lines together, free the memory. */ |
572 |
free(bbg.add_cmd_line); |
free(G.add_cmd_line); |
573 |
bbg.add_cmd_line = NULL; |
G.add_cmd_line = NULL; |
574 |
} |
} |
575 |
|
|
576 |
/* Append to a string, reallocating memory as necessary. */ |
/* Append to a string, reallocating memory as necessary. */ |
579 |
|
|
580 |
static void pipe_putc(char c) |
static void pipe_putc(char c) |
581 |
{ |
{ |
582 |
if (bbg.pipeline.idx == bbg.pipeline.len) { |
if (G.pipeline.idx == G.pipeline.len) { |
583 |
bbg.pipeline.buf = xrealloc(bbg.pipeline.buf, |
G.pipeline.buf = xrealloc(G.pipeline.buf, |
584 |
bbg.pipeline.len + PIPE_GROW); |
G.pipeline.len + PIPE_GROW); |
585 |
bbg.pipeline.len += PIPE_GROW; |
G.pipeline.len += PIPE_GROW; |
586 |
} |
} |
587 |
bbg.pipeline.buf[bbg.pipeline.idx++] = c; |
G.pipeline.buf[G.pipeline.idx++] = c; |
588 |
} |
} |
589 |
|
|
590 |
static void do_subst_w_backrefs(char *line, char *replace) |
static void do_subst_w_backrefs(char *line, char *replace) |
597 |
if (replace[i] == '\\') { |
if (replace[i] == '\\') { |
598 |
unsigned backref = replace[++i] - '0'; |
unsigned backref = replace[++i] - '0'; |
599 |
if (backref <= 9) { |
if (backref <= 9) { |
600 |
/* print out the text held in bbg.regmatch[backref] */ |
/* print out the text held in G.regmatch[backref] */ |
601 |
if (bbg.regmatch[backref].rm_so != -1) { |
if (G.regmatch[backref].rm_so != -1) { |
602 |
j = bbg.regmatch[backref].rm_so; |
j = G.regmatch[backref].rm_so; |
603 |
while (j < bbg.regmatch[backref].rm_eo) |
while (j < G.regmatch[backref].rm_eo) |
604 |
pipe_putc(line[j++]); |
pipe_putc(line[j++]); |
605 |
} |
} |
606 |
continue; |
continue; |
614 |
} |
} |
615 |
/* if we find an unescaped '&' print out the whole matched text. */ |
/* if we find an unescaped '&' print out the whole matched text. */ |
616 |
if (replace[i] == '&') { |
if (replace[i] == '&') { |
617 |
j = bbg.regmatch[0].rm_so; |
j = G.regmatch[0].rm_so; |
618 |
while (j < bbg.regmatch[0].rm_eo) |
while (j < G.regmatch[0].rm_eo) |
619 |
pipe_putc(line[j++]); |
pipe_putc(line[j++]); |
620 |
continue; |
continue; |
621 |
} |
} |
628 |
{ |
{ |
629 |
char *oldline = *line; |
char *oldline = *line; |
630 |
int altered = 0; |
int altered = 0; |
631 |
int match_count = 0; |
unsigned match_count = 0; |
632 |
regex_t *current_regex; |
regex_t *current_regex; |
633 |
|
|
634 |
/* Handle empty regex. */ |
/* Handle empty regex. */ |
635 |
if (sed_cmd->sub_match == NULL) { |
if (sed_cmd->sub_match == NULL) { |
636 |
current_regex = bbg.previous_regex_ptr; |
current_regex = G.previous_regex_ptr; |
637 |
if (!current_regex) |
if (!current_regex) |
638 |
bb_error_msg_and_die("no previous regexp"); |
bb_error_msg_and_die("no previous regexp"); |
639 |
} else |
} else |
640 |
bbg.previous_regex_ptr = current_regex = sed_cmd->sub_match; |
G.previous_regex_ptr = current_regex = sed_cmd->sub_match; |
641 |
|
|
642 |
/* Find the first match */ |
/* Find the first match */ |
643 |
if (REG_NOMATCH == regexec(current_regex, oldline, 10, bbg.regmatch, 0)) |
if (REG_NOMATCH == regexec(current_regex, oldline, 10, G.regmatch, 0)) |
644 |
return 0; |
return 0; |
645 |
|
|
646 |
/* Initialize temporary output buffer. */ |
/* Initialize temporary output buffer. */ |
647 |
bbg.pipeline.buf = xmalloc(PIPE_GROW); |
G.pipeline.buf = xmalloc(PIPE_GROW); |
648 |
bbg.pipeline.len = PIPE_GROW; |
G.pipeline.len = PIPE_GROW; |
649 |
bbg.pipeline.idx = 0; |
G.pipeline.idx = 0; |
650 |
|
|
651 |
/* Now loop through, substituting for matches */ |
/* Now loop through, substituting for matches */ |
652 |
do { |
do { |
656 |
echo " a.b" | busybox sed 's [^ .]* x g' |
echo " a.b" | busybox sed 's [^ .]* x g' |
657 |
The match_count check is so not to break |
The match_count check is so not to break |
658 |
echo "hi" | busybox sed 's/^/!/g' */ |
echo "hi" | busybox sed 's/^/!/g' */ |
659 |
if (!bbg.regmatch[0].rm_so && !bbg.regmatch[0].rm_eo && match_count) { |
if (!G.regmatch[0].rm_so && !G.regmatch[0].rm_eo && match_count) { |
660 |
pipe_putc(*oldline++); |
pipe_putc(*oldline++); |
661 |
continue; |
continue; |
662 |
} |
} |
665 |
|
|
666 |
/* If we aren't interested in this match, output old line to |
/* If we aren't interested in this match, output old line to |
667 |
end of match and continue */ |
end of match and continue */ |
668 |
if (sed_cmd->which_match && sed_cmd->which_match != match_count) { |
if (sed_cmd->which_match |
669 |
for (i = 0; i < bbg.regmatch[0].rm_eo; i++) |
&& (sed_cmd->which_match != match_count) |
670 |
|
) { |
671 |
|
for (i = 0; i < G.regmatch[0].rm_eo; i++) |
672 |
pipe_putc(*oldline++); |
pipe_putc(*oldline++); |
673 |
continue; |
continue; |
674 |
} |
} |
675 |
|
|
676 |
/* print everything before the match */ |
/* print everything before the match */ |
677 |
for (i = 0; i < bbg.regmatch[0].rm_so; i++) |
for (i = 0; i < G.regmatch[0].rm_so; i++) |
678 |
pipe_putc(oldline[i]); |
pipe_putc(oldline[i]); |
679 |
|
|
680 |
/* then print the substitution string */ |
/* then print the substitution string */ |
681 |
do_subst_w_backrefs(oldline, sed_cmd->string); |
do_subst_w_backrefs(oldline, sed_cmd->string); |
682 |
|
|
683 |
/* advance past the match */ |
/* advance past the match */ |
684 |
oldline += bbg.regmatch[0].rm_eo; |
oldline += G.regmatch[0].rm_eo; |
685 |
/* flag that something has changed */ |
/* flag that something has changed */ |
686 |
altered++; |
altered++; |
687 |
|
|
688 |
/* if we're not doing this globally, get out now */ |
/* if we're not doing this globally, get out now */ |
689 |
if (sed_cmd->which_match) break; |
if (sed_cmd->which_match) |
690 |
} while (*oldline && (regexec(current_regex, oldline, 10, bbg.regmatch, 0) != REG_NOMATCH)); |
break; |
691 |
|
} while (*oldline && (regexec(current_regex, oldline, 10, G.regmatch, 0) != REG_NOMATCH)); |
692 |
|
|
693 |
/* Copy rest of string into output pipeline */ |
/* Copy rest of string into output pipeline */ |
694 |
|
|
697 |
pipe_putc(0); |
pipe_putc(0); |
698 |
|
|
699 |
free(*line); |
free(*line); |
700 |
*line = bbg.pipeline.buf; |
*line = G.pipeline.buf; |
701 |
return altered; |
return altered; |
702 |
} |
} |
703 |
|
|
706 |
{ |
{ |
707 |
sed_cmd_t *sed_cmd; |
sed_cmd_t *sed_cmd; |
708 |
|
|
709 |
for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { |
for (sed_cmd = G.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { |
710 |
if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) { |
if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) { |
711 |
return sed_cmd; |
return sed_cmd; |
712 |
} |
} |
716 |
|
|
717 |
static void append(char *s) |
static void append(char *s) |
718 |
{ |
{ |
719 |
llist_add_to_end(&bbg.append_head, xstrdup(s)); |
llist_add_to_end(&G.append_head, xstrdup(s)); |
720 |
} |
} |
721 |
|
|
722 |
static void flush_append(void) |
static void flush_append(void) |
724 |
char *data; |
char *data; |
725 |
|
|
726 |
/* Output appended lines. */ |
/* Output appended lines. */ |
727 |
while ((data = (char *)llist_pop(&bbg.append_head))) { |
while ((data = (char *)llist_pop(&G.append_head))) { |
728 |
fprintf(bbg.nonstdout, "%s\n", data); |
fprintf(G.nonstdout, "%s\n", data); |
729 |
free(data); |
free(data); |
730 |
} |
} |
731 |
} |
} |
732 |
|
|
733 |
static void add_input_file(FILE *file) |
static void add_input_file(FILE *file) |
734 |
{ |
{ |
735 |
bbg.input_file_list = xrealloc(bbg.input_file_list, |
G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count); |
736 |
(bbg.input_file_count + 1) * sizeof(FILE *)); |
G.input_file_list[G.input_file_count++] = file; |
|
bbg.input_file_list[bbg.input_file_count++] = file; |
|
737 |
} |
} |
738 |
|
|
739 |
/* Get next line of input from bbg.input_file_list, flushing append buffer and |
/* Get next line of input from G.input_file_list, flushing append buffer and |
740 |
* noting if we ran out of files without a newline on the last line we read. |
* noting if we ran out of files without a newline on the last line we read. |
741 |
*/ |
*/ |
742 |
static char *get_next_line(int *last_char) |
enum { |
743 |
|
NO_EOL_CHAR = 1, |
744 |
|
LAST_IS_NUL = 2, |
745 |
|
}; |
746 |
|
static char *get_next_line(char *gets_char) |
747 |
{ |
{ |
748 |
char *temp = NULL; |
char *temp = NULL; |
749 |
int len, lc; |
int len; |
750 |
|
char gc; |
751 |
|
|
|
lc = 0; |
|
752 |
flush_append(); |
flush_append(); |
753 |
while (bbg.current_input_file < bbg.input_file_count) { |
|
754 |
|
/* will be returned if last line in the file |
755 |
|
* doesn't end with either '\n' or '\0' */ |
756 |
|
gc = NO_EOL_CHAR; |
757 |
|
while (G.current_input_file < G.input_file_count) { |
758 |
|
FILE *fp = G.input_file_list[G.current_input_file]; |
759 |
/* Read line up to a newline or NUL byte, inclusive, |
/* Read line up to a newline or NUL byte, inclusive, |
760 |
* return malloc'ed char[]. length of the chunk read |
* return malloc'ed char[]. length of the chunk read |
761 |
* is stored in len. NULL if EOF/error */ |
* is stored in len. NULL if EOF/error */ |
762 |
temp = bb_get_chunk_from_file( |
temp = bb_get_chunk_from_file(fp, &len); |
|
bbg.input_file_list[bbg.current_input_file], &len); |
|
763 |
if (temp) { |
if (temp) { |
764 |
/* len > 0 here, it's ok to do temp[len-1] */ |
/* len > 0 here, it's ok to do temp[len-1] */ |
765 |
char c = temp[len-1]; |
char c = temp[len-1]; |
766 |
if (c == '\n' || c == '\0') { |
if (c == '\n' || c == '\0') { |
767 |
temp[len-1] = '\0'; |
temp[len-1] = '\0'; |
768 |
lc |= (unsigned char)c; |
gc = c; |
769 |
break; |
if (c == '\0') { |
770 |
|
int ch = fgetc(fp); |
771 |
|
if (ch != EOF) |
772 |
|
ungetc(ch, fp); |
773 |
|
else |
774 |
|
gc = LAST_IS_NUL; |
775 |
|
} |
776 |
} |
} |
777 |
/* will be returned if last line in the file |
/* else we put NO_EOL_CHAR into *gets_char */ |
|
* doesn't end with either '\n' or '\0' */ |
|
|
lc |= 0x100; |
|
778 |
break; |
break; |
779 |
|
|
780 |
|
/* NB: I had the idea of peeking next file(s) and returning |
781 |
|
* NO_EOL_CHAR only if it is the *last* non-empty |
782 |
|
* input file. But there is a case where this won't work: |
783 |
|
* file1: "a woo\nb woo" |
784 |
|
* file2: "c no\nd no" |
785 |
|
* sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang" |
786 |
|
* (note: *no* newline after "b bang"!) */ |
787 |
} |
} |
788 |
/* Close this file and advance to next one */ |
/* Close this file and advance to next one */ |
789 |
fclose(bbg.input_file_list[bbg.current_input_file++]); |
fclose(fp); |
790 |
/* "this is the first line from new input file" */ |
G.current_input_file++; |
|
lc |= 0x200; |
|
791 |
} |
} |
792 |
*last_char = lc; |
*gets_char = gc; |
793 |
return temp; |
return temp; |
794 |
} |
} |
795 |
|
|
796 |
/* Output line of text. */ |
/* Output line of text. */ |
797 |
/* Note: |
/* Note: |
798 |
* The tricks with 0x200 and last_puts_char are there to emulate gnu sed. |
* The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed. |
799 |
* Without them, we had this: |
* Without them, we had this: |
800 |
* echo -n thingy >z1 |
* echo -n thingy >z1 |
801 |
* echo -n again >z2 |
* echo -n again >z2 |
807 |
* bbox: |
* bbox: |
808 |
* 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn| |
* 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn| |
809 |
*/ |
*/ |
810 |
|
static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char) |
|
static int puts_maybe_newline(char *s, FILE *file, int prev_last_char, int last_char) |
|
811 |
{ |
{ |
812 |
static char last_puts_char; |
char lpc = *last_puts_char; |
813 |
|
|
814 |
/* Is this a first line from new file |
/* Need to insert a '\n' between two files because first file's |
815 |
* and old file didn't end with '\n'? */ |
* last line wasn't terminated? */ |
816 |
if ((last_char & 0x200) && last_puts_char != '\n') { |
if (lpc != '\n' && lpc != '\0') { |
817 |
fputc('\n', file); |
fputc('\n', file); |
818 |
last_puts_char = '\n'; |
lpc = '\n'; |
819 |
} |
} |
820 |
fputs(s, file); |
fputs(s, file); |
821 |
/* why 'x'? - just something which is not '\n' */ |
|
822 |
|
/* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */ |
823 |
if (s[0]) |
if (s[0]) |
824 |
last_puts_char = 'x'; |
lpc = 'x'; |
825 |
if (!(last_char & 0x100)) { /* had trailing '\n' or '\0'? */ |
|
826 |
last_char &= 0xff; |
/* had trailing '\0' and it was last char of file? */ |
827 |
fputc(last_char, file); |
if (last_gets_char == LAST_IS_NUL) { |
828 |
last_puts_char = last_char; |
fputc('\0', file); |
829 |
|
lpc = 'x'; /* */ |
830 |
|
} else |
831 |
|
/* had trailing '\n' or '\0'? */ |
832 |
|
if (last_gets_char != NO_EOL_CHAR) { |
833 |
|
fputc(last_gets_char, file); |
834 |
|
lpc = last_gets_char; |
835 |
} |
} |
836 |
|
|
837 |
if (ferror(file)) { |
if (ferror(file)) { |
838 |
xfunc_error_retval = 4; /* It's what gnu sed exits with... */ |
xfunc_error_retval = 4; /* It's what gnu sed exits with... */ |
839 |
bb_error_msg_and_die(bb_msg_write_error); |
bb_error_msg_and_die(bb_msg_write_error); |
840 |
} |
} |
841 |
|
*last_puts_char = lpc; |
|
return last_char; |
|
842 |
} |
} |
843 |
|
|
844 |
#define sed_puts(s, n) \ |
#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n)) |
845 |
(prev_last_char = puts_maybe_newline(s, bbg.nonstdout, prev_last_char, n)) |
|
846 |
|
static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space) |
847 |
|
{ |
848 |
|
int retval = sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0); |
849 |
|
if (retval) |
850 |
|
G.previous_regex_ptr = sed_cmd->beg_match; |
851 |
|
return retval; |
852 |
|
} |
853 |
|
|
854 |
/* Process all the lines in all the files */ |
/* Process all the lines in all the files */ |
855 |
|
|
856 |
static void process_files(void) |
static void process_files(void) |
857 |
{ |
{ |
858 |
char *pattern_space, *next_line; |
char *pattern_space, *next_line; |
859 |
int linenum = 0, prev_last_char = 0; |
int linenum = 0; |
860 |
int last_char, next_last_char = 0; |
char last_puts_char = '\n'; |
861 |
|
char last_gets_char, next_gets_char; |
862 |
sed_cmd_t *sed_cmd; |
sed_cmd_t *sed_cmd; |
863 |
int substituted; |
int substituted; |
864 |
|
|
865 |
/* Prime the pump */ |
/* Prime the pump */ |
866 |
next_line = get_next_line(&next_last_char); |
next_line = get_next_line(&next_gets_char); |
867 |
|
|
868 |
/* go through every line in each file */ |
/* go through every line in each file */ |
869 |
again: |
again: |
870 |
substituted = 0; |
substituted = 0; |
871 |
|
|
872 |
/* Advance to next line. Stop if out of lines. */ |
/* Advance to next line. Stop if out of lines. */ |
873 |
pattern_space = next_line; |
pattern_space = next_line; |
874 |
if (!pattern_space) return; |
if (!pattern_space) return; |
875 |
last_char = next_last_char; |
last_gets_char = next_gets_char; |
876 |
|
|
877 |
/* Read one line in advance so we can act on the last line, |
/* Read one line in advance so we can act on the last line, |
878 |
* the '$' address */ |
* the '$' address */ |
879 |
next_line = get_next_line(&next_last_char); |
next_line = get_next_line(&next_gets_char); |
880 |
linenum++; |
linenum++; |
881 |
restart: |
restart: |
882 |
/* for every line, go through all the commands */ |
/* for every line, go through all the commands */ |
883 |
for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { |
for (sed_cmd = G.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { |
884 |
int old_matched, matched; |
int old_matched, matched; |
885 |
|
|
886 |
old_matched = sed_cmd->in_match; |
old_matched = sed_cmd->in_match; |
895 |
/* Or did we match the start of a numerical range? */ |
/* Or did we match the start of a numerical range? */ |
896 |
|| (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) |
|| (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) |
897 |
/* Or does this line match our begin address regex? */ |
/* Or does this line match our begin address regex? */ |
898 |
|| (sed_cmd->beg_match && |
|| (beg_match(sed_cmd, pattern_space)) |
|
!regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0)) |
|
899 |
/* Or did we match last line of input? */ |
/* Or did we match last line of input? */ |
900 |
|| (sed_cmd->beg_line == -1 && next_line == NULL); |
|| (sed_cmd->beg_line == -1 && next_line == NULL); |
901 |
|
|
917 |
/* or does this line matches our last address regex */ |
/* or does this line matches our last address regex */ |
918 |
|| (sed_cmd->end_match && old_matched |
|| (sed_cmd->end_match && old_matched |
919 |
&& (regexec(sed_cmd->end_match, |
&& (regexec(sed_cmd->end_match, |
920 |
pattern_space, 0, NULL, 0) == 0)) |
pattern_space, 0, NULL, 0) == 0)) |
921 |
); |
); |
922 |
} |
} |
923 |
|
|
924 |
/* Skip blocks of commands we didn't match. */ |
/* Skip blocks of commands we didn't match. */ |
925 |
if (sed_cmd->cmd == '{') { |
if (sed_cmd->cmd == '{') { |
926 |
if (sed_cmd->invert ? matched : !matched) |
if (sed_cmd->invert ? matched : !matched) { |
927 |
while (sed_cmd && sed_cmd->cmd != '}') |
while (sed_cmd->cmd != '}') { |
928 |
sed_cmd = sed_cmd->next; |
sed_cmd = sed_cmd->next; |
929 |
if (!sed_cmd) bb_error_msg_and_die("unterminated {"); |
if (!sed_cmd) |
930 |
|
bb_error_msg_and_die("unterminated {"); |
931 |
|
} |
932 |
|
} |
933 |
continue; |
continue; |
934 |
} |
} |
935 |
|
|
937 |
if (sed_cmd->invert ? !matched : matched) { |
if (sed_cmd->invert ? !matched : matched) { |
938 |
/* Update last used regex in case a blank substitute BRE is found */ |
/* Update last used regex in case a blank substitute BRE is found */ |
939 |
if (sed_cmd->beg_match) { |
if (sed_cmd->beg_match) { |
940 |
bbg.previous_regex_ptr = sed_cmd->beg_match; |
G.previous_regex_ptr = sed_cmd->beg_match; |
941 |
} |
} |
942 |
|
|
943 |
/* actual sedding */ |
/* actual sedding */ |
945 |
|
|
946 |
/* Print line number */ |
/* Print line number */ |
947 |
case '=': |
case '=': |
948 |
fprintf(bbg.nonstdout, "%d\n", linenum); |
fprintf(G.nonstdout, "%d\n", linenum); |
949 |
break; |
break; |
950 |
|
|
951 |
/* Write the current pattern space up to the first newline */ |
/* Write the current pattern space up to the first newline */ |
955 |
|
|
956 |
if (tmp) { |
if (tmp) { |
957 |
*tmp = '\0'; |
*tmp = '\0'; |
958 |
sed_puts(pattern_space, 1); |
/* TODO: explain why '\n' below */ |
959 |
|
sed_puts(pattern_space, '\n'); |
960 |
*tmp = '\n'; |
*tmp = '\n'; |
961 |
break; |
break; |
962 |
} |
} |
965 |
|
|
966 |
/* Write the current pattern space to output */ |
/* Write the current pattern space to output */ |
967 |
case 'p': |
case 'p': |
968 |
sed_puts(pattern_space, last_char); |
/* NB: we print this _before_ the last line |
969 |
|
* (of current file) is printed. Even if |
970 |
|
* that line is nonterminated, we print |
971 |
|
* '\n' here (gnu sed does the same) */ |
972 |
|
sed_puts(pattern_space, '\n'); |
973 |
break; |
break; |
974 |
/* Delete up through first newline */ |
/* Delete up through first newline */ |
975 |
case 'D': |
case 'D': |
995 |
|
|
996 |
/* handle p option */ |
/* handle p option */ |
997 |
if (sed_cmd->sub_p) |
if (sed_cmd->sub_p) |
998 |
sed_puts(pattern_space, last_char); |
sed_puts(pattern_space, last_gets_char); |
999 |
/* handle w option */ |
/* handle w option */ |
1000 |
if (sed_cmd->file) |
if (sed_cmd->sw_file) |
1001 |
sed_cmd->last_char = puts_maybe_newline( |
puts_maybe_newline( |
1002 |
pattern_space, sed_cmd->file, |
pattern_space, sed_cmd->sw_file, |
1003 |
sed_cmd->last_char, last_char); |
&sed_cmd->sw_last_char, last_gets_char); |
1004 |
break; |
break; |
1005 |
|
|
1006 |
/* Append line to linked list to be printed later */ |
/* Append line to linked list to be printed later */ |
1010 |
|
|
1011 |
/* Insert text before this line */ |
/* Insert text before this line */ |
1012 |
case 'i': |
case 'i': |
1013 |
sed_puts(sed_cmd->string, 1); |
sed_puts(sed_cmd->string, '\n'); |
1014 |
break; |
break; |
1015 |
|
|
1016 |
/* Cut and paste text (replace) */ |
/* Cut and paste text (replace) */ |
1017 |
case 'c': |
case 'c': |
1018 |
/* Only triggers on last line of a matching range. */ |
/* Only triggers on last line of a matching range. */ |
1019 |
if (!sed_cmd->in_match) |
if (!sed_cmd->in_match) |
1020 |
sed_puts(sed_cmd->string, 0); |
sed_puts(sed_cmd->string, NO_EOL_CHAR); |
1021 |
goto discard_line; |
goto discard_line; |
1022 |
|
|
1023 |
/* Read file, append contents to output */ |
/* Read file, append contents to output */ |
1025 |
{ |
{ |
1026 |
FILE *rfile; |
FILE *rfile; |
1027 |
|
|
1028 |
rfile = fopen(sed_cmd->string, "r"); |
rfile = fopen_for_read(sed_cmd->string); |
1029 |
if (rfile) { |
if (rfile) { |
1030 |
char *line; |
char *line; |
1031 |
|
|
1032 |
while ((line = xmalloc_getline(rfile)) |
while ((line = xmalloc_fgetline(rfile)) |
1033 |
!= NULL) |
!= NULL) |
1034 |
append(line); |
append(line); |
1035 |
xprint_and_close_file(rfile); |
xprint_and_close_file(rfile); |
1040 |
|
|
1041 |
/* Write pattern space to file. */ |
/* Write pattern space to file. */ |
1042 |
case 'w': |
case 'w': |
1043 |
sed_cmd->last_char = puts_maybe_newline( |
puts_maybe_newline( |
1044 |
pattern_space, sed_cmd->file, |
pattern_space, sed_cmd->sw_file, |
1045 |
sed_cmd->last_char, last_char); |
&sed_cmd->sw_last_char, last_gets_char); |
1046 |
break; |
break; |
1047 |
|
|
1048 |
/* Read next line from input */ |
/* Read next line from input */ |
1049 |
case 'n': |
case 'n': |
1050 |
if (!bbg.be_quiet) |
if (!G.be_quiet) |
1051 |
sed_puts(pattern_space, last_char); |
sed_puts(pattern_space, last_gets_char); |
1052 |
if (next_line) { |
if (next_line) { |
1053 |
free(pattern_space); |
free(pattern_space); |
1054 |
pattern_space = next_line; |
pattern_space = next_line; |
1055 |
last_char = next_last_char; |
last_gets_char = next_gets_char; |
1056 |
next_line = get_next_line(&next_last_char); |
next_line = get_next_line(&next_gets_char); |
1057 |
|
substituted = 0; |
1058 |
linenum++; |
linenum++; |
1059 |
break; |
break; |
1060 |
} |
} |
1083 |
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); |
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); |
1084 |
pattern_space[len] = '\n'; |
pattern_space[len] = '\n'; |
1085 |
strcpy(pattern_space + len+1, next_line); |
strcpy(pattern_space + len+1, next_line); |
1086 |
last_char = next_last_char; |
last_gets_char = next_gets_char; |
1087 |
next_line = get_next_line(&next_last_char); |
next_line = get_next_line(&next_gets_char); |
1088 |
linenum++; |
linenum++; |
1089 |
break; |
break; |
1090 |
} |
} |
1121 |
} |
} |
1122 |
case 'g': /* Replace pattern space with hold space */ |
case 'g': /* Replace pattern space with hold space */ |
1123 |
free(pattern_space); |
free(pattern_space); |
1124 |
pattern_space = xstrdup(bbg.hold_space ? bbg.hold_space : ""); |
pattern_space = xstrdup(G.hold_space ? G.hold_space : ""); |
1125 |
break; |
break; |
1126 |
case 'G': /* Append newline and hold space to pattern space */ |
case 'G': /* Append newline and hold space to pattern space */ |
1127 |
{ |
{ |
1130 |
|
|
1131 |
if (pattern_space) |
if (pattern_space) |
1132 |
pattern_space_size += strlen(pattern_space); |
pattern_space_size += strlen(pattern_space); |
1133 |
if (bbg.hold_space) |
if (G.hold_space) |
1134 |
hold_space_size = strlen(bbg.hold_space); |
hold_space_size = strlen(G.hold_space); |
1135 |
pattern_space = xrealloc(pattern_space, |
pattern_space = xrealloc(pattern_space, |
1136 |
pattern_space_size + hold_space_size); |
pattern_space_size + hold_space_size); |
1137 |
if (pattern_space_size == 2) |
if (pattern_space_size == 2) |
1138 |
pattern_space[0] = 0; |
pattern_space[0] = 0; |
1139 |
strcat(pattern_space, "\n"); |
strcat(pattern_space, "\n"); |
1140 |
if (bbg.hold_space) |
if (G.hold_space) |
1141 |
strcat(pattern_space, bbg.hold_space); |
strcat(pattern_space, G.hold_space); |
1142 |
last_char = '\n'; |
last_gets_char = '\n'; |
1143 |
|
|
1144 |
break; |
break; |
1145 |
} |
} |
1146 |
case 'h': /* Replace hold space with pattern space */ |
case 'h': /* Replace hold space with pattern space */ |
1147 |
free(bbg.hold_space); |
free(G.hold_space); |
1148 |
bbg.hold_space = xstrdup(pattern_space); |
G.hold_space = xstrdup(pattern_space); |
1149 |
break; |
break; |
1150 |
case 'H': /* Append newline and pattern space to hold space */ |
case 'H': /* Append newline and pattern space to hold space */ |
1151 |
{ |
{ |
1152 |
int hold_space_size = 2; |
int hold_space_size = 2; |
1153 |
int pattern_space_size = 0; |
int pattern_space_size = 0; |
1154 |
|
|
1155 |
if (bbg.hold_space) |
if (G.hold_space) |
1156 |
hold_space_size += strlen(bbg.hold_space); |
hold_space_size += strlen(G.hold_space); |
1157 |
if (pattern_space) |
if (pattern_space) |
1158 |
pattern_space_size = strlen(pattern_space); |
pattern_space_size = strlen(pattern_space); |
1159 |
bbg.hold_space = xrealloc(bbg.hold_space, |
G.hold_space = xrealloc(G.hold_space, |
1160 |
hold_space_size + pattern_space_size); |
hold_space_size + pattern_space_size); |
1161 |
|
|
1162 |
if (hold_space_size == 2) |
if (hold_space_size == 2) |
1163 |
*bbg.hold_space = 0; |
*G.hold_space = 0; |
1164 |
strcat(bbg.hold_space, "\n"); |
strcat(G.hold_space, "\n"); |
1165 |
if (pattern_space) |
if (pattern_space) |
1166 |
strcat(bbg.hold_space, pattern_space); |
strcat(G.hold_space, pattern_space); |
1167 |
|
|
1168 |
break; |
break; |
1169 |
} |
} |
1170 |
case 'x': /* Exchange hold and pattern space */ |
case 'x': /* Exchange hold and pattern space */ |
1171 |
{ |
{ |
1172 |
char *tmp = pattern_space; |
char *tmp = pattern_space; |
1173 |
pattern_space = bbg.hold_space ? : xzalloc(1); |
pattern_space = G.hold_space ? : xzalloc(1); |
1174 |
last_char = '\n'; |
last_gets_char = '\n'; |
1175 |
bbg.hold_space = tmp; |
G.hold_space = tmp; |
1176 |
break; |
break; |
1177 |
} |
} |
1178 |
} |
} |
1182 |
/* |
/* |
1183 |
* exit point from sedding... |
* exit point from sedding... |
1184 |
*/ |
*/ |
1185 |
discard_commands: |
discard_commands: |
1186 |
/* we will print the line unless we were told to be quiet ('-n') |
/* we will print the line unless we were told to be quiet ('-n') |
1187 |
or if the line was suppressed (ala 'd'elete) */ |
or if the line was suppressed (ala 'd'elete) */ |
1188 |
if (!bbg.be_quiet) sed_puts(pattern_space, last_char); |
if (!G.be_quiet) |
1189 |
|
sed_puts(pattern_space, last_gets_char); |
1190 |
|
|
1191 |
/* Delete and such jump here. */ |
/* Delete and such jump here. */ |
1192 |
discard_line: |
discard_line: |
1193 |
flush_append(); |
flush_append(); |
1194 |
free(pattern_space); |
free(pattern_space); |
1195 |
|
|
1197 |
} |
} |
1198 |
|
|
1199 |
/* It is possible to have a command line argument with embedded |
/* It is possible to have a command line argument with embedded |
1200 |
newlines. This counts as multiple command lines. */ |
* newlines. This counts as multiple command lines. |
1201 |
|
* However, newline can be escaped: 's/e/z\<newline>z/' |
1202 |
|
* We check for this. |
1203 |
|
*/ |
1204 |
|
|
1205 |
static void add_cmd_block(char *cmdstr) |
static void add_cmd_block(char *cmdstr) |
1206 |
{ |
{ |
1207 |
int go = 1; |
char *sv, *eol; |
|
char *temp = xstrdup(cmdstr), *temp2 = temp; |
|
|
|
|
|
while (go) { |
|
|
int len = strcspn(temp2, "\n"); |
|
|
if (!temp2[len]) go = 0; |
|
|
else temp2[len] = 0; |
|
|
add_cmd(temp2); |
|
|
temp2 += len+1; |
|
|
} |
|
|
free(temp); |
|
|
} |
|
|
|
|
|
static void add_cmds_link(llist_t *opt_e) |
|
|
{ |
|
|
if (!opt_e) return; |
|
|
add_cmds_link(opt_e->link); |
|
|
add_cmd_block(opt_e->data); |
|
|
free(opt_e); |
|
|
} |
|
1208 |
|
|
1209 |
static void add_files_link(llist_t *opt_f) |
cmdstr = sv = xstrdup(cmdstr); |
1210 |
{ |
do { |
1211 |
char *line; |
eol = strchr(cmdstr, '\n'); |
1212 |
FILE *cmdfile; |
next: |
1213 |
if (!opt_f) return; |
if (eol) { |
1214 |
add_files_link(opt_f->link); |
/* Count preceding slashes */ |
1215 |
cmdfile = xfopen(opt_f->data, "r"); |
int slashes = 0; |
1216 |
while ((line = xmalloc_getline(cmdfile)) != NULL) { |
char *sl = eol; |
1217 |
add_cmd(line); |
|
1218 |
free(line); |
while (sl != cmdstr && *--sl == '\\') |
1219 |
} |
slashes++; |
1220 |
xprint_and_close_file(cmdfile); |
/* Odd number of preceding slashes - newline is escaped */ |
1221 |
free(opt_f); |
if (slashes & 1) { |
1222 |
|
overlapping_strcpy(eol - 1, eol); |
1223 |
|
eol = strchr(eol, '\n'); |
1224 |
|
goto next; |
1225 |
|
} |
1226 |
|
*eol = '\0'; |
1227 |
|
} |
1228 |
|
add_cmd(cmdstr); |
1229 |
|
cmdstr = eol + 1; |
1230 |
|
} while (eol); |
1231 |
|
free(sv); |
1232 |
} |
} |
1233 |
|
|
1234 |
int sed_main(int argc, char **argv) |
int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
1235 |
|
int sed_main(int argc UNUSED_PARAM, char **argv) |
1236 |
{ |
{ |
1237 |
enum { |
enum { |
1238 |
OPT_in_place = 1 << 0, |
OPT_in_place = 1 << 0, |
1241 |
llist_t *opt_e, *opt_f; |
llist_t *opt_e, *opt_f; |
1242 |
int status = EXIT_SUCCESS; |
int status = EXIT_SUCCESS; |
1243 |
|
|
1244 |
bbg.sed_cmd_tail = &bbg.sed_cmd_head; |
INIT_G(); |
1245 |
|
|
1246 |
/* destroy command strings on exit */ |
/* destroy command strings on exit */ |
1247 |
if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff); |
if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff); |
1248 |
|
|
1249 |
/* Lie to autoconf when it starts asking stupid questions. */ |
/* Lie to autoconf when it starts asking stupid questions. */ |
1250 |
if (argc == 2 && !strcmp(argv[1], "--version")) { |
if (argv[1] && !strcmp(argv[1], "--version")) { |
1251 |
puts("This is not GNU sed version 4.0"); |
puts("This is not GNU sed version 4.0"); |
1252 |
return 0; |
return 0; |
1253 |
} |
} |
1256 |
opt_e = opt_f = NULL; |
opt_e = opt_f = NULL; |
1257 |
opt_complementary = "e::f::" /* can occur multiple times */ |
opt_complementary = "e::f::" /* can occur multiple times */ |
1258 |
"nn"; /* count -n */ |
"nn"; /* count -n */ |
1259 |
opt = getopt32(argc, argv, "irne:f:", &opt_e, &opt_f, |
opt = getopt32(argv, "irne:f:", &opt_e, &opt_f, |
1260 |
&bbg.be_quiet); /* counter for -n */ |
&G.be_quiet); /* counter for -n */ |
1261 |
argc -= optind; |
//argc -= optind; |
1262 |
argv += optind; |
argv += optind; |
1263 |
if (opt & OPT_in_place) { // -i |
if (opt & OPT_in_place) { // -i |
1264 |
atexit(cleanup_outname); |
atexit(cleanup_outname); |
1265 |
} |
} |
1266 |
if (opt & 0x2) bbg.regex_type |= REG_EXTENDED; // -r |
if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r |
1267 |
//if (opt & 0x4) bbg.be_quiet++; // -n |
//if (opt & 0x4) G.be_quiet++; // -n |
1268 |
if (opt & 0x8) { // -e |
while (opt_e) { // -e |
1269 |
/* getopt32 reverses order of arguments, handle it */ |
add_cmd_block(llist_pop(&opt_e)); |
1270 |
add_cmds_link(opt_e); |
} |
1271 |
} |
while (opt_f) { // -f |
1272 |
if (opt & 0x10) { // -f |
char *line; |
1273 |
/* getopt32 reverses order of arguments, handle it */ |
FILE *cmdfile; |
1274 |
add_files_link(opt_f); |
cmdfile = xfopen_for_read(llist_pop(&opt_f)); |
1275 |
|
while ((line = xmalloc_fgetline(cmdfile)) != NULL) { |
1276 |
|
add_cmd(line); |
1277 |
|
free(line); |
1278 |
|
} |
1279 |
|
fclose(cmdfile); |
1280 |
} |
} |
1281 |
/* if we didn't get a pattern from -e or -f, use argv[0] */ |
/* if we didn't get a pattern from -e or -f, use argv[0] */ |
1282 |
if (!(opt & 0x18)) { |
if (!(opt & 0x18)) { |
1283 |
if (!argc) |
if (!*argv) |
1284 |
bb_show_usage(); |
bb_show_usage(); |
1285 |
add_cmd_block(*argv++); |
add_cmd_block(*argv++); |
|
argc--; |
|
1286 |
} |
} |
1287 |
/* Flush any unfinished commands. */ |
/* Flush any unfinished commands. */ |
1288 |
add_cmd(""); |
add_cmd(""); |
1289 |
|
|
1290 |
/* By default, we write to stdout */ |
/* By default, we write to stdout */ |
1291 |
bbg.nonstdout = stdout; |
G.nonstdout = stdout; |
1292 |
|
|
1293 |
/* argv[0..(argc-1)] should be names of file to process. If no |
/* argv[0..(argc-1)] should be names of file to process. If no |
1294 |
* files were specified or '-' was specified, take input from stdin. |
* files were specified or '-' was specified, take input from stdin. |
1302 |
int i; |
int i; |
1303 |
FILE *file; |
FILE *file; |
1304 |
|
|
1305 |
for (i = 0; i < argc; i++) { |
for (i = 0; argv[i]; i++) { |
1306 |
struct stat statbuf; |
struct stat statbuf; |
1307 |
int nonstdoutfd; |
int nonstdoutfd; |
1308 |
|
|
1321 |
continue; |
continue; |
1322 |
} |
} |
1323 |
|
|
1324 |
bbg.outname = xasprintf("%sXXXXXX", argv[i]); |
G.outname = xasprintf("%sXXXXXX", argv[i]); |
1325 |
nonstdoutfd = mkstemp(bbg.outname); |
nonstdoutfd = mkstemp(G.outname); |
1326 |
if (-1 == nonstdoutfd) |
if (-1 == nonstdoutfd) |
1327 |
bb_error_msg_and_die("no temp file"); |
bb_perror_msg_and_die("cannot create temp file %s", G.outname); |
1328 |
bbg.nonstdout = fdopen(nonstdoutfd, "w"); |
G.nonstdout = fdopen(nonstdoutfd, "w"); |
1329 |
|
|
1330 |
/* Set permissions of output file */ |
/* Set permissions of output file */ |
1331 |
|
|
1333 |
fchmod(nonstdoutfd, statbuf.st_mode); |
fchmod(nonstdoutfd, statbuf.st_mode); |
1334 |
add_input_file(file); |
add_input_file(file); |
1335 |
process_files(); |
process_files(); |
1336 |
fclose(bbg.nonstdout); |
fclose(G.nonstdout); |
1337 |
|
|
1338 |
bbg.nonstdout = stdout; |
G.nonstdout = stdout; |
1339 |
/* unlink(argv[i]); */ |
/* unlink(argv[i]); */ |
1340 |
// FIXME: error check / message? |
xrename(G.outname, argv[i]); |
1341 |
rename(bbg.outname, argv[i]); |
free(G.outname); |
1342 |
free(bbg.outname); |
G.outname = NULL; |
|
bbg.outname = 0; |
|
1343 |
} |
} |
1344 |
if (bbg.input_file_count > bbg.current_input_file) |
if (G.input_file_count > G.current_input_file) |
1345 |
process_files(); |
process_files(); |
1346 |
} |
} |
1347 |
|
|