source: MondoRescue/branches/3.3/mindi-busybox/editors/sed.c@ 3865

Last change on this file since 3865 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 42.7 KB
Line 
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
9 * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
10 *
11 * MAINTAINER: Rob Landley <rob@landley.net>
12 *
13 * Licensed under GPLv2, see file LICENSE in this source tree.
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 * (G.sed_cmd_head/G.sed_cmd_tail).
25 *
26 * process_files() does actual sedding, reading data lines from each input FILE*
27 * (which could be stdin) and applying the sed command list (sed_cmd_head) to
28 * each of the resulting lines.
29 *
30 * sed_main() is where external code calls into this, with a command line.
31 */
32
33/* Supported features and commands in this version of sed:
34 *
35 * - comments ('#')
36 * - address matching: num|/matchstr/[,num|/matchstr/|$]command
37 * - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
38 * - edit commands: (a)ppend, (i)nsert, (c)hange
39 * - file commands: (r)ead
40 * - backreferences in substitution expressions (\0, \1, \2...\9)
41 * - grouped commands: {cmd1;cmd2}
42 * - transliteration (y/source-chars/dest-chars/)
43 * - pattern space hold space storing / swapping (g, h, x)
44 * - labels / branching (: label, b, t, T)
45 *
46 * (Note: Specifying an address (range) to match is *optional*; commands
47 * default to the whole pattern space if no specific address match was
48 * requested.)
49 *
50 * Todo:
51 * - Create a wrapper around regex to make libc's regex conform with sed
52 *
53 * Reference
54 * http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
55 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
56 * http://sed.sourceforge.net/sedfaq3.html
57 */
58
59//config:config SED
60//config: bool "sed"
61//config: default y
62//config: help
63//config: sed is used to perform text transformations on a file
64//config: or input from a pipeline.
65
66//kbuild:lib-$(CONFIG_SED) += sed.o
67
68//applet:IF_SED(APPLET(sed, BB_DIR_BIN, BB_SUID_DROP))
69
70//usage:#define sed_trivial_usage
71//usage: "[-inrE] [-f FILE]... [-e CMD]... [FILE]...\n"
72//usage: "or: sed [-inrE] CMD [FILE]..."
73//usage:#define sed_full_usage "\n\n"
74//usage: " -e CMD Add CMD to sed commands to be executed"
75//usage: "\n -f FILE Add FILE contents to sed commands to be executed"
76//usage: "\n -i[SFX] Edit files in-place (otherwise sends to stdout)"
77//usage: "\n Optionally back files up, appending SFX"
78//usage: "\n -n Suppress automatic printing of pattern space"
79//usage: "\n -r,-E Use extended regex syntax"
80//usage: "\n"
81//usage: "\nIf no -e or -f, the first non-option argument is the sed command string."
82//usage: "\nRemaining arguments are input files (stdin if none)."
83//usage:
84//usage:#define sed_example_usage
85//usage: "$ echo \"foo\" | sed -e 's/f[a-zA-Z]o/bar/g'\n"
86//usage: "bar\n"
87
88#include "libbb.h"
89#include "common_bufsiz.h"
90#include "xregex.h"
91
92#if 0
93# define dbg(...) bb_error_msg(__VA_ARGS__)
94#else
95# define dbg(...) ((void)0)
96#endif
97
98
99enum {
100 OPT_in_place = 1 << 0,
101};
102
103/* Each sed command turns into one of these structures. */
104typedef struct sed_cmd_s {
105 /* Ordered by alignment requirements: currently 36 bytes on x86 */
106 struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
107
108 /* address storage */
109 regex_t *beg_match; /* sed -e '/match/cmd' */
110 regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */
111 regex_t *sub_match; /* For 's/sub_match/string/' */
112 int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
113 int beg_line_orig; /* copy of the above, needed for -i */
114 int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($). -2-N = +N */
115 int end_line_orig;
116
117 FILE *sw_file; /* File (sw) command writes to, NULL for none. */
118 char *string; /* Data string for (saicytb) commands. */
119
120 unsigned which_match; /* (s) Which match to replace (0 for all) */
121
122 /* Bitfields (gcc won't group them if we don't) */
123 unsigned invert:1; /* the '!' after the address */
124 unsigned in_match:1; /* Next line also included in match? */
125 unsigned sub_p:1; /* (s) print option */
126
127 char sw_last_char; /* Last line written by (sw) had no '\n' */
128
129 /* GENERAL FIELDS */
130 char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
131} sed_cmd_t;
132
133static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
134
135struct globals {
136 /* options */
137 int be_quiet, regex_type;
138
139 FILE *nonstdout;
140 char *outname, *hold_space;
141 smallint exitcode;
142
143 /* list of input files */
144 int current_input_file, last_input_file;
145 char **input_file_list;
146 FILE *current_fp;
147
148 regmatch_t regmatch[10];
149 regex_t *previous_regex_ptr;
150
151 /* linked list of sed commands */
152 sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
153
154 /* linked list of append lines */
155 llist_t *append_head;
156
157 char *add_cmd_line;
158
159 struct pipeline {
160 char *buf; /* Space to hold string */
161 int idx; /* Space used */
162 int len; /* Space allocated */
163 } pipeline;
164} FIX_ALIASING;
165#define G (*(struct globals*)bb_common_bufsiz1)
166#define INIT_G() do { \
167 setup_common_bufsiz(); \
168 BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
169 G.sed_cmd_tail = &G.sed_cmd_head; \
170} while (0)
171
172
173#if ENABLE_FEATURE_CLEAN_UP
174static void sed_free_and_close_stuff(void)
175{
176 sed_cmd_t *sed_cmd = G.sed_cmd_head;
177
178 llist_free(G.append_head, free);
179
180 while (sed_cmd) {
181 sed_cmd_t *sed_cmd_next = sed_cmd->next;
182
183 if (sed_cmd->sw_file)
184 fclose(sed_cmd->sw_file);
185
186 if (sed_cmd->beg_match) {
187 regfree(sed_cmd->beg_match);
188 free(sed_cmd->beg_match);
189 }
190 if (sed_cmd->end_match) {
191 regfree(sed_cmd->end_match);
192 free(sed_cmd->end_match);
193 }
194 if (sed_cmd->sub_match) {
195 regfree(sed_cmd->sub_match);
196 free(sed_cmd->sub_match);
197 }
198 free(sed_cmd->string);
199 free(sed_cmd);
200 sed_cmd = sed_cmd_next;
201 }
202
203 free(G.hold_space);
204
205 if (G.current_fp)
206 fclose(G.current_fp);
207}
208#else
209void sed_free_and_close_stuff(void);
210#endif
211
212/* If something bad happens during -i operation, delete temp file */
213
214static void cleanup_outname(void)
215{
216 if (G.outname) unlink(G.outname);
217}
218
219/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */
220
221static unsigned parse_escapes(char *dest, const char *string, int len, char from, char to)
222{
223 char *d = dest;
224 int i = 0;
225
226 if (len == -1)
227 len = strlen(string);
228
229 while (i < len) {
230 if (string[i] == '\\') {
231 if (!to || string[i+1] == from) {
232 if ((*d = to ? to : string[i+1]) == '\0')
233 return d - dest;
234 i += 2;
235 d++;
236 continue;
237 }
238 i++; /* skip backslash in string[] */
239 *d++ = '\\';
240 /* fall through: copy next char verbatim */
241 }
242 if ((*d = string[i++]) == '\0')
243 return d - dest;
244 d++;
245 }
246 *d = '\0';
247 return d - dest;
248}
249
250static char *copy_parsing_escapes(const char *string, int len)
251{
252 const char *s;
253 char *dest = xmalloc(len + 1);
254
255 /* sed recognizes \n */
256 /* GNU sed also recognizes \t and \r */
257 for (s = "\nn\tt\rr"; *s; s += 2) {
258 len = parse_escapes(dest, string, len, s[1], s[0]);
259 string = dest;
260 }
261 return dest;
262}
263
264
265/*
266 * index_of_next_unescaped_regexp_delim - walks left to right through a string
267 * beginning at a specified index and returns the index of the next regular
268 * expression delimiter (typically a forward slash ('/')) not preceded by
269 * a backslash ('\'). A negative delimiter disables square bracket checking.
270 */
271static int index_of_next_unescaped_regexp_delim(int delimiter, const char *str)
272{
273 int bracket = -1;
274 int escaped = 0;
275 int idx = 0;
276 char ch;
277
278 if (delimiter < 0) {
279 bracket--;
280 delimiter = -delimiter;
281 }
282
283 for (; (ch = str[idx]) != '\0'; idx++) {
284 if (bracket >= 0) {
285 if (ch == ']'
286 && !(bracket == idx - 1 || (bracket == idx - 2 && str[idx - 1] == '^'))
287 ) {
288 bracket = -1;
289 }
290 } else if (escaped)
291 escaped = 0;
292 else if (ch == '\\')
293 escaped = 1;
294 else if (bracket == -1 && ch == '[')
295 bracket = idx;
296 else if (ch == delimiter)
297 return idx;
298 }
299
300 /* if we make it to here, we've hit the end of the string */
301 bb_error_msg_and_die("unmatched '%c'", delimiter);
302}
303
304/*
305 * Returns the index of the third delimiter
306 */
307static int parse_regex_delim(const char *cmdstr, char **match, char **replace)
308{
309 const char *cmdstr_ptr = cmdstr;
310 unsigned char delimiter;
311 int idx = 0;
312
313 /* verify that the 's' or 'y' is followed by something. That something
314 * (typically a 'slash') is now our regexp delimiter... */
315 if (*cmdstr == '\0')
316 bb_error_msg_and_die("bad format in substitution expression");
317 delimiter = *cmdstr_ptr++;
318
319 /* save the match string */
320 idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
321 *match = copy_parsing_escapes(cmdstr_ptr, idx);
322
323 /* save the replacement string */
324 cmdstr_ptr += idx + 1;
325 idx = index_of_next_unescaped_regexp_delim(- (int)delimiter, cmdstr_ptr);
326 *replace = copy_parsing_escapes(cmdstr_ptr, idx);
327
328 return ((cmdstr_ptr - cmdstr) + idx);
329}
330
331/*
332 * returns the index in the string just past where the address ends.
333 */
334static int get_address(const char *my_str, int *linenum, regex_t ** regex)
335{
336 const char *pos = my_str;
337
338 if (isdigit(*my_str)) {
339 *linenum = strtol(my_str, (char**)&pos, 10);
340 /* endstr shouldnt ever equal NULL */
341 } else if (*my_str == '$') {
342 *linenum = -1;
343 pos++;
344 } else if (*my_str == '/' || *my_str == '\\') {
345 int next;
346 char delimiter;
347 char *temp;
348
349 delimiter = '/';
350 if (*my_str == '\\')
351 delimiter = *++pos;
352 next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
353 temp = copy_parsing_escapes(pos, next);
354 *regex = xzalloc(sizeof(regex_t));
355 xregcomp(*regex, temp, G.regex_type);
356 free(temp);
357 /* Move position to next character after last delimiter */
358 pos += (next+1);
359 }
360 return pos - my_str;
361}
362
363/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */
364static int parse_file_cmd(/*sed_cmd_t *sed_cmd,*/ const char *filecmdstr, char **retval)
365{
366 int start = 0, idx, hack = 0;
367
368 /* Skip whitespace, then grab filename to end of line */
369 while (isspace(filecmdstr[start]))
370 start++;
371 idx = start;
372 while (filecmdstr[idx] && filecmdstr[idx] != '\n')
373 idx++;
374
375 /* If lines glued together, put backslash back. */
376 if (filecmdstr[idx] == '\n')
377 hack = 1;
378 if (idx == start)
379 bb_error_msg_and_die("empty filename");
380 *retval = xstrndup(filecmdstr+start, idx-start+hack+1);
381 if (hack)
382 (*retval)[idx] = '\\';
383
384 return idx;
385}
386
387static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr)
388{
389 int cflags = G.regex_type;
390 char *match;
391 int idx;
392
393 /*
394 * A substitution command should look something like this:
395 * s/match/replace/ #giIpw
396 * || | |||
397 * mandatory optional
398 */
399 idx = parse_regex_delim(substr, &match, &sed_cmd->string);
400
401 /* determine the number of back references in the match string */
402 /* Note: we compute this here rather than in the do_subst_command()
403 * function to save processor time, at the expense of a little more memory
404 * (4 bits) per sed_cmd */
405
406 /* process the flags */
407
408 sed_cmd->which_match = 1;
409 dbg("s flags:'%s'", substr + idx + 1);
410 while (substr[++idx]) {
411 dbg("s flag:'%c'", substr[idx]);
412 /* Parse match number */
413 if (isdigit(substr[idx])) {
414 if (match[0] != '^') {
415 /* Match 0 treated as all, multiple matches we take the last one. */
416 const char *pos = substr + idx;
417/* FIXME: error check? */
418 sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &pos, 10);
419 idx = pos - substr - 1;
420 }
421 continue;
422 }
423 /* Skip spaces */
424 if (isspace(substr[idx]))
425 continue;
426
427 switch (substr[idx]) {
428 /* Replace all occurrences */
429 case 'g':
430 if (match[0] != '^')
431 sed_cmd->which_match = 0;
432 break;
433 /* Print pattern space */
434 case 'p':
435 sed_cmd->sub_p = 1;
436 break;
437 /* Write to file */
438 case 'w':
439 {
440 char *fname;
441 idx += parse_file_cmd(/*sed_cmd,*/ substr+idx+1, &fname);
442 sed_cmd->sw_file = xfopen_for_write(fname);
443 sed_cmd->sw_last_char = '\n';
444 free(fname);
445 break;
446 }
447 /* Ignore case (gnu exension) */
448 case 'i':
449 case 'I':
450 cflags |= REG_ICASE;
451 break;
452 /* Comment */
453 case '#':
454 // while (substr[++idx]) continue;
455 idx += strlen(substr + idx); // same
456 /* Fall through */
457 /* End of command */
458 case ';':
459 case '}':
460 goto out;
461 default:
462 dbg("s bad flags:'%s'", substr + idx);
463 bb_error_msg_and_die("bad option in substitution expression");
464 }
465 }
466 out:
467 /* compile the match string into a regex */
468 if (*match != '\0') {
469 /* If match is empty, we use last regex used at runtime */
470 sed_cmd->sub_match = xzalloc(sizeof(regex_t));
471 dbg("xregcomp('%s',%x)", match, cflags);
472 xregcomp(sed_cmd->sub_match, match, cflags);
473 dbg("regcomp ok");
474 }
475 free(match);
476
477 return idx;
478}
479
480/*
481 * Process the commands arguments
482 */
483static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const char *cmdstr)
484{
485 static const char cmd_letters[] ALIGN1 = "saicrw:btTydDgGhHlnNpPqx={}";
486 enum {
487 IDX_s = 0,
488 IDX_a,
489 IDX_i,
490 IDX_c,
491 IDX_r,
492 IDX_w,
493 IDX_colon,
494 IDX_b,
495 IDX_t,
496 IDX_T,
497 IDX_y,
498 IDX_d,
499 IDX_D,
500 IDX_g,
501 IDX_G,
502 IDX_h,
503 IDX_H,
504 IDX_l,
505 IDX_n,
506 IDX_N,
507 IDX_p,
508 IDX_P,
509 IDX_q,
510 IDX_x,
511 IDX_equal,
512 IDX_lbrace,
513 IDX_rbrace,
514 IDX_nul
515 };
516 unsigned idx;
517
518 BUILD_BUG_ON(sizeof(cmd_letters)-1 != IDX_nul);
519
520 idx = strchrnul(cmd_letters, sed_cmd->cmd) - cmd_letters;
521
522 /* handle (s)ubstitution command */
523 if (idx == IDX_s) {
524 cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
525 }
526 /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
527 else if (idx <= IDX_c) { /* a,i,c */
528 unsigned len;
529
530 if (idx < IDX_c) { /* a,i */
531 if (sed_cmd->end_line || sed_cmd->end_match)
532 bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
533 }
534 for (;;) {
535 if (*cmdstr == '\n' || *cmdstr == '\\') {
536 cmdstr++;
537 break;
538 }
539 if (!isspace(*cmdstr))
540 break;
541 cmdstr++;
542 }
543 len = strlen(cmdstr);
544 sed_cmd->string = copy_parsing_escapes(cmdstr, len);
545 cmdstr += len;
546 /* "\anychar" -> "anychar" */
547 parse_escapes(sed_cmd->string, sed_cmd->string, -1, '\0', '\0');
548 }
549 /* handle file cmds: (r)ead */
550 else if (idx <= IDX_w) { /* r,w */
551 if (idx < IDX_w) { /* r */
552 if (sed_cmd->end_line || sed_cmd->end_match)
553 bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
554 }
555 cmdstr += parse_file_cmd(/*sed_cmd,*/ cmdstr, &sed_cmd->string);
556 if (sed_cmd->cmd == 'w') {
557 sed_cmd->sw_file = xfopen_for_write(sed_cmd->string);
558 sed_cmd->sw_last_char = '\n';
559 }
560 }
561 /* handle branch commands */
562 else if (idx <= IDX_T) { /* :,b,t,T */
563 int length;
564
565 cmdstr = skip_whitespace(cmdstr);
566 length = strcspn(cmdstr, semicolon_whitespace);
567 if (length) {
568 sed_cmd->string = xstrndup(cmdstr, length);
569 cmdstr += length;
570 }
571 }
572 /* translation command */
573 else if (idx == IDX_y) {
574 char *match, *replace;
575 int i = cmdstr[0];
576
577 cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1;
578 /* \n already parsed, but \delimiter needs unescaping. */
579 parse_escapes(match, match, -1, i, i);
580 parse_escapes(replace, replace, -1, i, i);
581
582 sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
583 for (i = 0; match[i] && replace[i]; i++) {
584 sed_cmd->string[i*2] = match[i];
585 sed_cmd->string[i*2+1] = replace[i];
586 }
587 free(match);
588 free(replace);
589 }
590 /* if it wasnt a single-letter command that takes no arguments
591 * then it must be an invalid command.
592 */
593 else if (idx >= IDX_nul) { /* not d,D,g,G,h,H,l,n,N,p,P,q,x,=,{,} */
594 bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd);
595 }
596
597 /* give back whatever's left over */
598 return cmdstr;
599}
600
601
602/* Parse address+command sets, skipping comment lines. */
603
604static void add_cmd(const char *cmdstr)
605{
606 sed_cmd_t *sed_cmd;
607 unsigned len, n;
608
609 /* Append this line to any unfinished line from last time. */
610 if (G.add_cmd_line) {
611 char *tp = xasprintf("%s\n%s", G.add_cmd_line, cmdstr);
612 free(G.add_cmd_line);
613 cmdstr = G.add_cmd_line = tp;
614 }
615
616 /* If this line ends with unescaped backslash, request next line. */
617 n = len = strlen(cmdstr);
618 while (n && cmdstr[n-1] == '\\')
619 n--;
620 if ((len - n) & 1) { /* if odd number of trailing backslashes */
621 if (!G.add_cmd_line)
622 G.add_cmd_line = xstrdup(cmdstr);
623 G.add_cmd_line[len-1] = '\0';
624 return;
625 }
626
627 /* Loop parsing all commands in this line. */
628 while (*cmdstr) {
629 /* Skip leading whitespace and semicolons */
630 cmdstr += strspn(cmdstr, semicolon_whitespace);
631
632 /* If no more commands, exit. */
633 if (!*cmdstr) break;
634
635 /* if this is a comment, jump past it and keep going */
636 if (*cmdstr == '#') {
637 /* "#n" is the same as using -n on the command line */
638 if (cmdstr[1] == 'n')
639 G.be_quiet++;
640 cmdstr = strpbrk(cmdstr, "\n\r");
641 if (!cmdstr) break;
642 continue;
643 }
644
645 /* parse the command
646 * format is: [addr][,addr][!]cmd
647 * |----||-----||-|
648 * part1 part2 part3
649 */
650
651 sed_cmd = xzalloc(sizeof(sed_cmd_t));
652
653 /* first part (if present) is an address: either a '$', a number or a /regex/ */
654 cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
655 sed_cmd->beg_line_orig = sed_cmd->beg_line;
656
657 /* second part (if present) will begin with a comma */
658 if (*cmdstr == ',') {
659 int idx;
660
661 cmdstr++;
662 if (*cmdstr == '+' && isdigit(cmdstr[1])) {
663 /* http://sed.sourceforge.net/sedfaq3.html#s3.3
664 * Under GNU sed 3.02+, ssed, and sed15+, <address2>
665 * may also be a notation of the form +num,
666 * indicating the next num lines after <address1> is
667 * matched.
668 * GNU sed 4.2.1 accepts even "+" (meaning "+0").
669 * We don't (we check for isdigit, see above), think
670 * about the "+-3" case.
671 */
672 char *end;
673 /* code is smaller compared to using &cmdstr here: */
674 idx = strtol(cmdstr+1, &end, 10);
675 sed_cmd->end_line = -2 - idx;
676 cmdstr = end;
677 } else {
678 idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
679 cmdstr += idx;
680 idx--; /* if 0, trigger error check below */
681 }
682 if (idx < 0)
683 bb_error_msg_and_die("no address after comma");
684 sed_cmd->end_line_orig = sed_cmd->end_line;
685 }
686
687 /* skip whitespace before the command */
688 cmdstr = skip_whitespace(cmdstr);
689
690 /* Check for inversion flag */
691 if (*cmdstr == '!') {
692 sed_cmd->invert = 1;
693 cmdstr++;
694
695 /* skip whitespace before the command */
696 cmdstr = skip_whitespace(cmdstr);
697 }
698
699 /* last part (mandatory) will be a command */
700 if (!*cmdstr)
701 bb_error_msg_and_die("missing command");
702 sed_cmd->cmd = *cmdstr++;
703 cmdstr = parse_cmd_args(sed_cmd, cmdstr);
704
705 /* cmdstr now points past args.
706 * GNU sed requires a separator, if there are more commands,
707 * else it complains "char N: extra characters after command".
708 * Example: "sed 'p;d'". We also allow "sed 'pd'".
709 */
710
711 /* Add the command to the command array */
712 *G.sed_cmd_tail = sed_cmd;
713 G.sed_cmd_tail = &sed_cmd->next;
714 }
715
716 /* If we glued multiple lines together, free the memory. */
717 free(G.add_cmd_line);
718 G.add_cmd_line = NULL;
719}
720
721/* Append to a string, reallocating memory as necessary. */
722
723#define PIPE_GROW 64
724
725static void pipe_putc(char c)
726{
727 if (G.pipeline.idx == G.pipeline.len) {
728 G.pipeline.buf = xrealloc(G.pipeline.buf,
729 G.pipeline.len + PIPE_GROW);
730 G.pipeline.len += PIPE_GROW;
731 }
732 G.pipeline.buf[G.pipeline.idx++] = c;
733}
734
735static void do_subst_w_backrefs(char *line, char *replace)
736{
737 int i, j;
738
739 /* go through the replacement string */
740 for (i = 0; replace[i]; i++) {
741 /* if we find a backreference (\1, \2, etc.) print the backref'ed text */
742 if (replace[i] == '\\') {
743 unsigned backref = replace[++i] - '0';
744 if (backref <= 9) {
745 /* print out the text held in G.regmatch[backref] */
746 if (G.regmatch[backref].rm_so != -1) {
747 j = G.regmatch[backref].rm_so;
748 while (j < G.regmatch[backref].rm_eo)
749 pipe_putc(line[j++]);
750 }
751 continue;
752 }
753 /* I _think_ it is impossible to get '\' to be
754 * the last char in replace string. Thus we dont check
755 * for replace[i] == NUL. (counterexample anyone?) */
756 /* if we find a backslash escaped character, print the character */
757 pipe_putc(replace[i]);
758 continue;
759 }
760 /* if we find an unescaped '&' print out the whole matched text. */
761 if (replace[i] == '&') {
762 j = G.regmatch[0].rm_so;
763 while (j < G.regmatch[0].rm_eo)
764 pipe_putc(line[j++]);
765 continue;
766 }
767 /* Otherwise just output the character. */
768 pipe_putc(replace[i]);
769 }
770}
771
772static int do_subst_command(sed_cmd_t *sed_cmd, char **line_p)
773{
774 char *line = *line_p;
775 unsigned match_count = 0;
776 bool altered = 0;
777 bool prev_match_empty = 1;
778 bool tried_at_eol = 0;
779 regex_t *current_regex;
780
781 current_regex = sed_cmd->sub_match;
782 /* Handle empty regex. */
783 if (!current_regex) {
784 current_regex = G.previous_regex_ptr;
785 if (!current_regex)
786 bb_error_msg_and_die("no previous regexp");
787 }
788 G.previous_regex_ptr = current_regex;
789
790 /* Find the first match */
791 dbg("matching '%s'", line);
792 if (REG_NOMATCH == regexec(current_regex, line, 10, G.regmatch, 0)) {
793 dbg("no match");
794 return 0;
795 }
796 dbg("match");
797
798 /* Initialize temporary output buffer. */
799 G.pipeline.buf = xmalloc(PIPE_GROW);
800 G.pipeline.len = PIPE_GROW;
801 G.pipeline.idx = 0;
802
803 /* Now loop through, substituting for matches */
804 do {
805 int start = G.regmatch[0].rm_so;
806 int end = G.regmatch[0].rm_eo;
807 int i;
808
809 match_count++;
810
811 /* If we aren't interested in this match, output old line to
812 * end of match and continue */
813 if (sed_cmd->which_match
814 && (sed_cmd->which_match != match_count)
815 ) {
816 for (i = 0; i < end; i++)
817 pipe_putc(*line++);
818 /* Null match? Print one more char */
819 if (start == end && *line)
820 pipe_putc(*line++);
821 goto next;
822 }
823
824 /* Print everything before the match */
825 for (i = 0; i < start; i++)
826 pipe_putc(line[i]);
827
828 /* Then print the substitution string,
829 * unless we just matched empty string after non-empty one.
830 * Example: string "cccd", pattern "c*", repl "R":
831 * result is "RdR", not "RRdR": first match "ccc",
832 * second is "" before "d", third is "" after "d".
833 * Second match is NOT replaced!
834 */
835 if (prev_match_empty || start != 0 || start != end) {
836 //dbg("%d %d %d", prev_match_empty, start, end);
837 dbg("inserting replacement at %d in '%s'", start, line);
838 do_subst_w_backrefs(line, sed_cmd->string);
839 /* Flag that something has changed */
840 altered = 1;
841 } else {
842 dbg("NOT inserting replacement at %d in '%s'", start, line);
843 }
844
845 /* If matched string is empty (f.e. "c*" pattern),
846 * copy verbatim one char after it before attempting more matches
847 */
848 prev_match_empty = (start == end);
849 if (prev_match_empty) {
850 if (!line[end]) {
851 tried_at_eol = 1;
852 } else {
853 pipe_putc(line[end]);
854 end++;
855 }
856 }
857
858 /* Advance past the match */
859 dbg("line += %d", end);
860 line += end;
861
862 /* if we're not doing this globally, get out now */
863 if (sed_cmd->which_match != 0)
864 break;
865 next:
866 /* Exit if we are at EOL and already tried matching at it */
867 if (*line == '\0') {
868 if (tried_at_eol)
869 break;
870 tried_at_eol = 1;
871 }
872
873//maybe (end ? REG_NOTBOL : 0) instead of unconditional REG_NOTBOL?
874 } while (regexec(current_regex, line, 10, G.regmatch, REG_NOTBOL) != REG_NOMATCH);
875
876 /* Copy rest of string into output pipeline */
877 while (1) {
878 char c = *line++;
879 pipe_putc(c);
880 if (c == '\0')
881 break;
882 }
883
884 free(*line_p);
885 *line_p = G.pipeline.buf;
886 return altered;
887}
888
889/* Set command pointer to point to this label. (Does not handle null label.) */
890static sed_cmd_t *branch_to(char *label)
891{
892 sed_cmd_t *sed_cmd;
893
894 for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
895 if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) {
896 return sed_cmd;
897 }
898 }
899 bb_error_msg_and_die("can't find label for jump to '%s'", label);
900}
901
902static void append(char *s)
903{
904 llist_add_to_end(&G.append_head, s);
905}
906
907/* Output line of text. */
908/* Note:
909 * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
910 * Without them, we had this:
911 * echo -n thingy >z1
912 * echo -n again >z2
913 * >znull
914 * sed "s/i/z/" z1 z2 znull | hexdump -vC
915 * output:
916 * gnu sed 4.1.5:
917 * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn|
918 * bbox:
919 * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
920 */
921enum {
922 NO_EOL_CHAR = 1,
923 LAST_IS_NUL = 2,
924};
925static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
926{
927 char lpc = *last_puts_char;
928
929 /* Need to insert a '\n' between two files because first file's
930 * last line wasn't terminated? */
931 if (lpc != '\n' && lpc != '\0') {
932 fputc('\n', file);
933 lpc = '\n';
934 }
935 fputs(s, file);
936
937 /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
938 if (s[0])
939 lpc = 'x';
940
941 /* had trailing '\0' and it was last char of file? */
942 if (last_gets_char == LAST_IS_NUL) {
943 fputc('\0', file);
944 lpc = 'x'; /* */
945 } else
946 /* had trailing '\n' or '\0'? */
947 if (last_gets_char != NO_EOL_CHAR) {
948 fputc(last_gets_char, file);
949 lpc = last_gets_char;
950 }
951
952 if (ferror(file)) {
953 xfunc_error_retval = 4; /* It's what gnu sed exits with... */
954 bb_error_msg_and_die(bb_msg_write_error);
955 }
956 *last_puts_char = lpc;
957}
958
959static void flush_append(char *last_puts_char)
960{
961 char *data;
962
963 /* Output appended lines. */
964 while ((data = (char *)llist_pop(&G.append_head)) != NULL) {
965 /* Append command does not respect "nonterminated-ness"
966 * of last line. Try this:
967 * $ echo -n "woot" | sed -e '/woot/a woo' -
968 * woot
969 * woo
970 * (both lines are terminated with \n)
971 * Therefore we do not propagate "last_gets_char" here,
972 * pass '\n' instead:
973 */
974 puts_maybe_newline(data, G.nonstdout, last_puts_char, '\n');
975 free(data);
976 }
977}
978
979/* Get next line of input from G.input_file_list, flushing append buffer and
980 * noting if we ran out of files without a newline on the last line we read.
981 */
982static char *get_next_line(char *gets_char, char *last_puts_char)
983{
984 char *temp = NULL;
985 int len;
986 char gc;
987
988 flush_append(last_puts_char);
989
990 /* will be returned if last line in the file
991 * doesn't end with either '\n' or '\0' */
992 gc = NO_EOL_CHAR;
993 for (; G.current_input_file <= G.last_input_file; G.current_input_file++) {
994 FILE *fp = G.current_fp;
995 if (!fp) {
996 const char *path = G.input_file_list[G.current_input_file];
997 fp = stdin;
998 if (path != bb_msg_standard_input) {
999 fp = fopen_or_warn(path, "r");
1000 if (!fp) {
1001 G.exitcode = EXIT_FAILURE;
1002 continue;
1003 }
1004 }
1005 G.current_fp = fp;
1006 }
1007 /* Read line up to a newline or NUL byte, inclusive,
1008 * return malloc'ed char[]. length of the chunk read
1009 * is stored in len. NULL if EOF/error */
1010 temp = bb_get_chunk_from_file(fp, &len);
1011 if (temp) {
1012 /* len > 0 here, it's ok to do temp[len-1] */
1013 char c = temp[len-1];
1014 if (c == '\n' || c == '\0') {
1015 temp[len-1] = '\0';
1016 gc = c;
1017 if (c == '\0') {
1018 int ch = fgetc(fp);
1019 if (ch != EOF)
1020 ungetc(ch, fp);
1021 else
1022 gc = LAST_IS_NUL;
1023 }
1024 }
1025 /* else we put NO_EOL_CHAR into *gets_char */
1026 break;
1027
1028 /* NB: I had the idea of peeking next file(s) and returning
1029 * NO_EOL_CHAR only if it is the *last* non-empty
1030 * input file. But there is a case where this won't work:
1031 * file1: "a woo\nb woo"
1032 * file2: "c no\nd no"
1033 * sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang"
1034 * (note: *no* newline after "b bang"!) */
1035 }
1036 /* Close this file and advance to next one */
1037 fclose_if_not_stdin(fp);
1038 G.current_fp = NULL;
1039 }
1040 *gets_char = gc;
1041 return temp;
1042}
1043
1044#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n))
1045
1046static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space)
1047{
1048 int retval = sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0);
1049 if (retval)
1050 G.previous_regex_ptr = sed_cmd->beg_match;
1051 return retval;
1052}
1053
1054/* Process all the lines in all the files */
1055
1056static void process_files(void)
1057{
1058 char *pattern_space, *next_line;
1059 int linenum = 0;
1060 char last_puts_char = '\n';
1061 char last_gets_char, next_gets_char;
1062 sed_cmd_t *sed_cmd;
1063 int substituted;
1064
1065 /* Prime the pump */
1066 next_line = get_next_line(&next_gets_char, &last_puts_char);
1067
1068 /* Go through every line in each file */
1069 again:
1070 substituted = 0;
1071
1072 /* Advance to next line. Stop if out of lines. */
1073 pattern_space = next_line;
1074 if (!pattern_space)
1075 return;
1076 last_gets_char = next_gets_char;
1077
1078 /* Read one line in advance so we can act on the last line,
1079 * the '$' address */
1080 next_line = get_next_line(&next_gets_char, &last_puts_char);
1081 linenum++;
1082
1083 /* For every line, go through all the commands */
1084 restart:
1085 for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
1086 int old_matched, matched;
1087
1088 old_matched = sed_cmd->in_match;
1089
1090 /* Determine if this command matches this line: */
1091
1092 dbg("match1:%d", sed_cmd->in_match);
1093 dbg("match2:%d", (!sed_cmd->beg_line && !sed_cmd->end_line
1094 && !sed_cmd->beg_match && !sed_cmd->end_match));
1095 dbg("match3:%d", (sed_cmd->beg_line > 0
1096 && (sed_cmd->end_line || sed_cmd->end_match
1097 ? (sed_cmd->beg_line <= linenum)
1098 : (sed_cmd->beg_line == linenum)
1099 )
1100 ));
1101 dbg("match4:%d", (beg_match(sed_cmd, pattern_space)));
1102 dbg("match5:%d", (sed_cmd->beg_line == -1 && next_line == NULL));
1103
1104 /* Are we continuing a previous multi-line match? */
1105 sed_cmd->in_match = sed_cmd->in_match
1106 /* Or is no range necessary? */
1107 || (!sed_cmd->beg_line && !sed_cmd->end_line
1108 && !sed_cmd->beg_match && !sed_cmd->end_match)
1109 /* Or did we match the start of a numerical range? */
1110 || (sed_cmd->beg_line > 0
1111 && (sed_cmd->end_line || sed_cmd->end_match
1112 /* note: even if end is numeric and is < linenum too,
1113 * GNU sed matches! We match too, therefore we don't
1114 * check here that linenum <= end.
1115 * Example:
1116 * printf '1\n2\n3\n4\n' | sed -n '1{N;N;d};1p;2,3p;3p;4p'
1117 * first three input lines are deleted;
1118 * 4th line is matched and printed
1119 * by "2,3" (!) and by "4" ranges
1120 */
1121 ? (sed_cmd->beg_line <= linenum) /* N,end */
1122 : (sed_cmd->beg_line == linenum) /* N */
1123 )
1124 )
1125 /* Or does this line match our begin address regex? */
1126 || (beg_match(sed_cmd, pattern_space))
1127 /* Or did we match last line of input? */
1128 || (sed_cmd->beg_line == -1 && next_line == NULL);
1129
1130 /* Snapshot the value */
1131 matched = sed_cmd->in_match;
1132
1133 dbg("cmd:'%c' matched:%d beg_line:%d end_line:%d linenum:%d",
1134 sed_cmd->cmd, matched, sed_cmd->beg_line, sed_cmd->end_line, linenum);
1135
1136 /* Is this line the end of the current match? */
1137
1138 if (matched) {
1139 if (sed_cmd->end_line <= -2) {
1140 /* address2 is +N, i.e. N lines from beg_line */
1141 sed_cmd->end_line = linenum + (-sed_cmd->end_line - 2);
1142 }
1143 /* once matched, "n,xxx" range is dead, disabling it */
1144 if (sed_cmd->beg_line > 0) {
1145 sed_cmd->beg_line = -2;
1146 }
1147 dbg("end1:%d", sed_cmd->end_line ? sed_cmd->end_line == -1
1148 ? !next_line : (sed_cmd->end_line <= linenum)
1149 : !sed_cmd->end_match);
1150 dbg("end2:%d", sed_cmd->end_match && old_matched
1151 && !regexec(sed_cmd->end_match,pattern_space, 0, NULL, 0));
1152 sed_cmd->in_match = !(
1153 /* has the ending line come, or is this a single address command? */
1154 (sed_cmd->end_line
1155 ? sed_cmd->end_line == -1
1156 ? !next_line
1157 : (sed_cmd->end_line <= linenum)
1158 : !sed_cmd->end_match
1159 )
1160 /* or does this line matches our last address regex */
1161 || (sed_cmd->end_match && old_matched
1162 && (regexec(sed_cmd->end_match,
1163 pattern_space, 0, NULL, 0) == 0)
1164 )
1165 );
1166 }
1167
1168 /* Skip blocks of commands we didn't match */
1169 if (sed_cmd->cmd == '{') {
1170 if (sed_cmd->invert ? matched : !matched) {
1171 unsigned nest_cnt = 0;
1172 while (1) {
1173 if (sed_cmd->cmd == '{')
1174 nest_cnt++;
1175 if (sed_cmd->cmd == '}') {
1176 nest_cnt--;
1177 if (nest_cnt == 0)
1178 break;
1179 }
1180 sed_cmd = sed_cmd->next;
1181 if (!sed_cmd)
1182 bb_error_msg_and_die("unterminated {");
1183 }
1184 }
1185 continue;
1186 }
1187
1188 /* Okay, so did this line match? */
1189 if (sed_cmd->invert ? matched : !matched)
1190 continue; /* no */
1191
1192 /* Update last used regex in case a blank substitute BRE is found */
1193 if (sed_cmd->beg_match) {
1194 G.previous_regex_ptr = sed_cmd->beg_match;
1195 }
1196
1197 /* actual sedding */
1198 dbg("pattern_space:'%s' next_line:'%s' cmd:%c",
1199 pattern_space, next_line, sed_cmd->cmd);
1200 switch (sed_cmd->cmd) {
1201
1202 /* Print line number */
1203 case '=':
1204 fprintf(G.nonstdout, "%d\n", linenum);
1205 break;
1206
1207 /* Write the current pattern space up to the first newline */
1208 case 'P':
1209 {
1210 char *tmp = strchr(pattern_space, '\n');
1211 if (tmp) {
1212 *tmp = '\0';
1213 /* TODO: explain why '\n' below */
1214 sed_puts(pattern_space, '\n');
1215 *tmp = '\n';
1216 break;
1217 }
1218 /* Fall Through */
1219 }
1220
1221 /* Write the current pattern space to output */
1222 case 'p':
1223 /* NB: we print this _before_ the last line
1224 * (of current file) is printed. Even if
1225 * that line is nonterminated, we print
1226 * '\n' here (gnu sed does the same) */
1227 sed_puts(pattern_space, '\n');
1228 break;
1229 /* Delete up through first newline */
1230 case 'D':
1231 {
1232 char *tmp = strchr(pattern_space, '\n');
1233 if (tmp) {
1234 overlapping_strcpy(pattern_space, tmp + 1);
1235 goto restart;
1236 }
1237 }
1238 /* discard this line. */
1239 case 'd':
1240 goto discard_line;
1241
1242 /* Substitute with regex */
1243 case 's':
1244 if (!do_subst_command(sed_cmd, &pattern_space))
1245 break;
1246 dbg("do_subst_command succeeded:'%s'", pattern_space);
1247 substituted |= 1;
1248
1249 /* handle p option */
1250 if (sed_cmd->sub_p)
1251 sed_puts(pattern_space, last_gets_char);
1252 /* handle w option */
1253 if (sed_cmd->sw_file)
1254 puts_maybe_newline(
1255 pattern_space, sed_cmd->sw_file,
1256 &sed_cmd->sw_last_char, last_gets_char);
1257 break;
1258
1259 /* Append line to linked list to be printed later */
1260 case 'a':
1261 append(xstrdup(sed_cmd->string));
1262 break;
1263
1264 /* Insert text before this line */
1265 case 'i':
1266 sed_puts(sed_cmd->string, '\n');
1267 break;
1268
1269 /* Cut and paste text (replace) */
1270 case 'c':
1271 /* Only triggers on last line of a matching range. */
1272 if (!sed_cmd->in_match)
1273 sed_puts(sed_cmd->string, '\n');
1274 goto discard_line;
1275
1276 /* Read file, append contents to output */
1277 case 'r':
1278 {
1279 FILE *rfile;
1280 rfile = fopen_for_read(sed_cmd->string);
1281 if (rfile) {
1282 char *line;
1283 while ((line = xmalloc_fgetline(rfile))
1284 != NULL)
1285 append(line);
1286 fclose(rfile);
1287 }
1288
1289 break;
1290 }
1291
1292 /* Write pattern space to file. */
1293 case 'w':
1294 puts_maybe_newline(
1295 pattern_space, sed_cmd->sw_file,
1296 &sed_cmd->sw_last_char, last_gets_char);
1297 break;
1298
1299 /* Read next line from input */
1300 case 'n':
1301 if (!G.be_quiet)
1302 sed_puts(pattern_space, last_gets_char);
1303 if (next_line) {
1304 free(pattern_space);
1305 pattern_space = next_line;
1306 last_gets_char = next_gets_char;
1307 next_line = get_next_line(&next_gets_char, &last_puts_char);
1308 substituted = 0;
1309 linenum++;
1310 break;
1311 }
1312 /* fall through */
1313
1314 /* Quit. End of script, end of input. */
1315 case 'q':
1316 /* Exit the outer while loop */
1317 free(next_line);
1318 next_line = NULL;
1319 goto discard_commands;
1320
1321 /* Append the next line to the current line */
1322 case 'N':
1323 {
1324 int len;
1325 /* If no next line, jump to end of script and exit. */
1326 /* http://www.gnu.org/software/sed/manual/sed.html:
1327 * "Most versions of sed exit without printing anything
1328 * when the N command is issued on the last line of
1329 * a file. GNU sed prints pattern space before exiting
1330 * unless of course the -n command switch has been
1331 * specified. This choice is by design."
1332 */
1333 if (next_line == NULL) {
1334 //goto discard_line;
1335 goto discard_commands; /* GNU behavior */
1336 }
1337 /* Append next_line, read new next_line. */
1338 len = strlen(pattern_space);
1339 pattern_space = xrealloc(pattern_space, len + strlen(next_line) + 2);
1340 pattern_space[len] = '\n';
1341 strcpy(pattern_space + len+1, next_line);
1342 last_gets_char = next_gets_char;
1343 next_line = get_next_line(&next_gets_char, &last_puts_char);
1344 linenum++;
1345 break;
1346 }
1347
1348 /* Test/branch if substitution occurred */
1349 case 't':
1350 if (!substituted) break;
1351 substituted = 0;
1352 /* Fall through */
1353 /* Test/branch if substitution didn't occur */
1354 case 'T':
1355 if (substituted) break;
1356 /* Fall through */
1357 /* Branch to label */
1358 case 'b':
1359 if (!sed_cmd->string) goto discard_commands;
1360 else sed_cmd = branch_to(sed_cmd->string);
1361 break;
1362 /* Transliterate characters */
1363 case 'y':
1364 {
1365 int i, j;
1366 for (i = 0; pattern_space[i]; i++) {
1367 for (j = 0; sed_cmd->string[j]; j += 2) {
1368 if (pattern_space[i] == sed_cmd->string[j]) {
1369 pattern_space[i] = sed_cmd->string[j + 1];
1370 break;
1371 }
1372 }
1373 }
1374
1375 break;
1376 }
1377 case 'g': /* Replace pattern space with hold space */
1378 free(pattern_space);
1379 pattern_space = xstrdup(G.hold_space ? G.hold_space : "");
1380 break;
1381 case 'G': /* Append newline and hold space to pattern space */
1382 {
1383 int pattern_space_size = 2;
1384 int hold_space_size = 0;
1385
1386 if (pattern_space)
1387 pattern_space_size += strlen(pattern_space);
1388 if (G.hold_space)
1389 hold_space_size = strlen(G.hold_space);
1390 pattern_space = xrealloc(pattern_space,
1391 pattern_space_size + hold_space_size);
1392 if (pattern_space_size == 2)
1393 pattern_space[0] = 0;
1394 strcat(pattern_space, "\n");
1395 if (G.hold_space)
1396 strcat(pattern_space, G.hold_space);
1397 last_gets_char = '\n';
1398
1399 break;
1400 }
1401 case 'h': /* Replace hold space with pattern space */
1402 free(G.hold_space);
1403 G.hold_space = xstrdup(pattern_space);
1404 break;
1405 case 'H': /* Append newline and pattern space to hold space */
1406 {
1407 int hold_space_size = 2;
1408 int pattern_space_size = 0;
1409
1410 if (G.hold_space)
1411 hold_space_size += strlen(G.hold_space);
1412 if (pattern_space)
1413 pattern_space_size = strlen(pattern_space);
1414 G.hold_space = xrealloc(G.hold_space,
1415 hold_space_size + pattern_space_size);
1416
1417 if (hold_space_size == 2)
1418 *G.hold_space = 0;
1419 strcat(G.hold_space, "\n");
1420 if (pattern_space)
1421 strcat(G.hold_space, pattern_space);
1422
1423 break;
1424 }
1425 case 'x': /* Exchange hold and pattern space */
1426 {
1427 char *tmp = pattern_space;
1428 pattern_space = G.hold_space ? G.hold_space : xzalloc(1);
1429 last_gets_char = '\n';
1430 G.hold_space = tmp;
1431 break;
1432 }
1433 } /* switch */
1434 } /* for each cmd */
1435
1436 /*
1437 * Exit point from sedding...
1438 */
1439 discard_commands:
1440 /* we will print the line unless we were told to be quiet ('-n')
1441 or if the line was suppressed (ala 'd'elete) */
1442 if (!G.be_quiet)
1443 sed_puts(pattern_space, last_gets_char);
1444
1445 /* Delete and such jump here. */
1446 discard_line:
1447 flush_append(&last_puts_char /*,last_gets_char*/);
1448 free(pattern_space);
1449
1450 goto again;
1451}
1452
1453/* It is possible to have a command line argument with embedded
1454 * newlines. This counts as multiple command lines.
1455 * However, newline can be escaped: 's/e/z\<newline>z/'
1456 * add_cmd() handles this.
1457 */
1458
1459static void add_cmd_block(char *cmdstr)
1460{
1461 char *sv, *eol;
1462
1463 cmdstr = sv = xstrdup(cmdstr);
1464 do {
1465 eol = strchr(cmdstr, '\n');
1466 if (eol)
1467 *eol = '\0';
1468 add_cmd(cmdstr);
1469 cmdstr = eol + 1;
1470 } while (eol);
1471 free(sv);
1472}
1473
1474int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1475int sed_main(int argc UNUSED_PARAM, char **argv)
1476{
1477 unsigned opt;
1478 llist_t *opt_e, *opt_f;
1479 char *opt_i;
1480
1481#if ENABLE_LONG_OPTS
1482 static const char sed_longopts[] ALIGN1 =
1483 /* name has_arg short */
1484 "in-place\0" Optional_argument "i"
1485 "regexp-extended\0" No_argument "r"
1486 "quiet\0" No_argument "n"
1487 "silent\0" No_argument "n"
1488 "expression\0" Required_argument "e"
1489 "file\0" Required_argument "f";
1490#endif
1491
1492 INIT_G();
1493
1494 /* destroy command strings on exit */
1495 if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);
1496
1497 /* Lie to autoconf when it starts asking stupid questions. */
1498 if (argv[1] && strcmp(argv[1], "--version") == 0) {
1499 puts("This is not GNU sed version 4.0");
1500 return 0;
1501 }
1502
1503 /* do normal option parsing */
1504 opt_e = opt_f = NULL;
1505 opt_i = NULL;
1506 opt_complementary = "e::f::" /* can occur multiple times */
1507 "nn"; /* count -n */
1508
1509 IF_LONG_OPTS(applet_long_options = sed_longopts);
1510
1511 /* -i must be first, to match OPT_in_place definition */
1512 /* -E is a synonym of -r:
1513 * GNU sed 4.2.1 mentions it in neither --help
1514 * nor manpage, but does recognize it.
1515 */
1516 opt = getopt32(argv, "i::rEne:f:", &opt_i, &opt_e, &opt_f,
1517 &G.be_quiet); /* counter for -n */
1518 //argc -= optind;
1519 argv += optind;
1520 if (opt & OPT_in_place) { // -i
1521 atexit(cleanup_outname);
1522 }
1523 if (opt & (2|4))
1524 G.regex_type |= REG_EXTENDED; // -r or -E
1525 //if (opt & 8)
1526 // G.be_quiet++; // -n (implemented with a counter instead)
1527 while (opt_e) { // -e
1528 add_cmd_block(llist_pop(&opt_e));
1529 }
1530 while (opt_f) { // -f
1531 char *line;
1532 FILE *cmdfile;
1533 cmdfile = xfopen_stdin(llist_pop(&opt_f));
1534 while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
1535 add_cmd(line);
1536 free(line);
1537 }
1538 fclose_if_not_stdin(cmdfile);
1539 }
1540 /* if we didn't get a pattern from -e or -f, use argv[0] */
1541 if (!(opt & 0x30)) {
1542 if (!*argv)
1543 bb_show_usage();
1544 add_cmd_block(*argv++);
1545 }
1546 /* Flush any unfinished commands. */
1547 add_cmd("");
1548
1549 /* By default, we write to stdout */
1550 G.nonstdout = stdout;
1551
1552 /* argv[0..(argc-1)] should be names of file to process. If no
1553 * files were specified or '-' was specified, take input from stdin.
1554 * Otherwise, we process all the files specified. */
1555 G.input_file_list = argv;
1556 if (!argv[0]) {
1557 if (opt & OPT_in_place)
1558 bb_error_msg_and_die(bb_msg_requires_arg, "-i");
1559 argv[0] = (char*)bb_msg_standard_input;
1560 /* G.last_input_file = 0; - already is */
1561 } else {
1562 goto start;
1563
1564 for (; *argv; argv++) {
1565 struct stat statbuf;
1566 int nonstdoutfd;
1567 sed_cmd_t *sed_cmd;
1568
1569 G.last_input_file++;
1570 start:
1571 if (!(opt & OPT_in_place)) {
1572 if (LONE_DASH(*argv)) {
1573 *argv = (char*)bb_msg_standard_input;
1574 process_files();
1575 }
1576 continue;
1577 }
1578
1579 /* -i: process each FILE separately: */
1580
1581 if (stat(*argv, &statbuf) != 0) {
1582 bb_simple_perror_msg(*argv);
1583 G.exitcode = EXIT_FAILURE;
1584 G.current_input_file++;
1585 continue;
1586 }
1587 G.outname = xasprintf("%sXXXXXX", *argv);
1588 nonstdoutfd = xmkstemp(G.outname);
1589 G.nonstdout = xfdopen_for_write(nonstdoutfd);
1590 /* Set permissions/owner of output file */
1591 /* chmod'ing AFTER chown would preserve suid/sgid bits,
1592 * but GNU sed 4.2.1 does not preserve them either */
1593 fchmod(nonstdoutfd, statbuf.st_mode);
1594 fchown(nonstdoutfd, statbuf.st_uid, statbuf.st_gid);
1595
1596 process_files();
1597 fclose(G.nonstdout);
1598 G.nonstdout = stdout;
1599
1600 if (opt_i) {
1601 char *backupname = xasprintf("%s%s", *argv, opt_i);
1602 xrename(*argv, backupname);
1603 free(backupname);
1604 }
1605 /* else unlink(*argv); - rename below does this */
1606 xrename(G.outname, *argv); //TODO: rollback backup on error?
1607 free(G.outname);
1608 G.outname = NULL;
1609
1610 /* Fix disabled range matches and mangled ",+N" ranges */
1611 for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
1612 sed_cmd->beg_line = sed_cmd->beg_line_orig;
1613 sed_cmd->end_line = sed_cmd->end_line_orig;
1614 }
1615 }
1616 /* Here, to handle "sed 'cmds' nonexistent_file" case we did:
1617 * if (G.current_input_file[G.current_input_file] == NULL)
1618 * return G.exitcode;
1619 * but it's not needed since process_files() works correctly
1620 * in this case too. */
1621 }
1622
1623 process_files();
1624
1625 return G.exitcode;
1626}
Note: See TracBrowser for help on using the repository browser.