source: branches/2.2.2/mindi-busybox/editors/sed.c @ 1247

Last change on this file since 1247 was 902, checked in by Bruno Cornec, 14 years ago

mindi-busybox now uses busybox 1.2.2 (Thanks Rob for that last update and good lucck for your future projects).

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