/* vi: set sw=4 ts=4: */ /* * sed.c - very minimalist version of sed * * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org> * Copyright (C) 2002 Matt Kraai * Copyright (C) 2003 by Glenn McGrath <bug1@iinet.net.au> * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net> * * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ /* Code overview. Files are laid out to avoid unnecessary function declarations. So for example, every function add_cmd calls occurs before add_cmd in this file. add_cmd() is called on each line of sed command text (from a file or from the command line). It calls get_address() and parse_cmd_args(). The resulting sed_cmd_t structures are appended to a linked list (sed_cmd_head/sed_cmd_tail). add_input_file() adds a FILE * to the list of input files. We need to know all input sources ahead of time to find the last line for the $ match. process_files() does actual sedding, reading data lines from each input FILE * (which could be stdin) and applying the sed command list (sed_cmd_head) to each of the resulting lines. sed_main() is where external code calls into this, with a command line. */ /* Supported features and commands in this version of sed: - comments ('#') - address matching: num|/matchstr/[,num|/matchstr/|$]command - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags) - edit commands: (a)ppend, (i)nsert, (c)hange - file commands: (r)ead - backreferences in substitution expressions (\0, \1, \2...\9) - grouped commands: {cmd1;cmd2} - transliteration (y/source-chars/dest-chars/) - pattern space hold space storing / swapping (g, h, x) - labels / branching (: label, b, t, T) (Note: Specifying an address (range) to match is *optional*; commands default to the whole pattern space if no specific address match was requested.) Todo: - Create a wrapper around regex to make libc's regex conform with sed Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html */ #include <stdio.h> #include <unistd.h> /* for getopt() */ #include <string.h> /* for strdup() */ #include <errno.h> #include <ctype.h> /* for isspace() */ #include <stdlib.h> #include "busybox.h" #include "xregex.h" typedef struct sed_cmd_s { /* Ordered by alignment requirements: currently 36 bytes on x86 */ /* address storage */ regex_t *beg_match; /* sed -e '/match/cmd' */ regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */ regex_t *sub_match; /* For 's/sub_match/string/' */ int beg_line; /* 'sed 1p' 0 == apply commands to all lines */ int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */ FILE *file; /* File (sr) command writes to, -1 for none. */ char *string; /* Data string for (saicytb) commands. */ unsigned short which_match; /* (s) Which match to replace (0 for all) */ /* Bitfields (gcc won't group them if we don't) */ unsigned int invert:1; /* the '!' after the address */ unsigned int in_match:1; /* Next line also included in match? */ unsigned int no_newline:1; /* Last line written by (sr) had no '\n' */ unsigned int sub_p:1; /* (s) print option */ /* GENERAL FIELDS */ char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */ struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */ } sed_cmd_t; /* globals */ /* options */ static int be_quiet, in_place, regex_type; static FILE *nonstdout; static char *outname,*hold_space; /* List of input files */ static int input_file_count,current_input_file; static FILE **input_file_list; static const char bad_format_in_subst[] = "bad format in substitution expression"; static const char *const semicolon_whitespace = "; \n\r\t\v"; static regmatch_t regmatch[10]; static regex_t *previous_regex_ptr; /* linked list of sed commands */ static sed_cmd_t sed_cmd_head; static sed_cmd_t *sed_cmd_tail = &sed_cmd_head; /* Linked list of append lines */ struct append_list { char *string; struct append_list *next; }; static struct append_list *append_head=NULL, *append_tail=NULL; #ifdef CONFIG_FEATURE_CLEAN_UP static void free_and_close_stuff(void) { sed_cmd_t *sed_cmd = sed_cmd_head.next; while(append_head) { append_tail=append_head->next; free(append_head->string); free(append_head); append_head=append_tail; } while (sed_cmd) { sed_cmd_t *sed_cmd_next = sed_cmd->next; if(sed_cmd->file) bb_xprint_and_close_file(sed_cmd->file); if (sed_cmd->beg_match) { regfree(sed_cmd->beg_match); free(sed_cmd->beg_match); } if (sed_cmd->end_match) { regfree(sed_cmd->end_match); free(sed_cmd->end_match); } if (sed_cmd->sub_match) { regfree(sed_cmd->sub_match); free(sed_cmd->sub_match); } free(sed_cmd->string); free(sed_cmd); sed_cmd = sed_cmd_next; } if(hold_space) free(hold_space); while(current_input_file<input_file_count) fclose(input_file_list[current_input_file++]); } #endif /* If something bad happens during -i operation, delete temp file */ static void cleanup_outname(void) { if(outname) unlink(outname); } /* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */ static void parse_escapes(char *dest, const char *string, int len, char from, char to) { int i=0; while(i<len) { if(string[i] == '\\') { if(!to || string[i+1] == from) { *(dest++) = to ? to : string[i+1]; i+=2; continue; } else *(dest++)=string[i++]; } *(dest++) = string[i++]; } *dest=0; } static char *copy_parsing_escapes(const char *string, int len) { char *dest=xmalloc(len+1); parse_escapes(dest,string,len,'n','\n'); return dest; } /* * index_of_next_unescaped_regexp_delim - walks left to right through a string * beginning at a specified index and returns the index of the next regular * expression delimiter (typically a forward * slash ('/')) not preceded by * a backslash ('\'). */ static int index_of_next_unescaped_regexp_delim(const char delimiter, const char *str) { int bracket = -1; int escaped = 0; int idx = 0; char ch; for (; (ch = str[idx]); idx++) { if (bracket != -1) { if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2 && str[idx - 1] == '^'))) bracket = -1; } else if (escaped) escaped = 0; else if (ch == '\\') escaped = 1; else if (ch == '[') bracket = idx; else if (ch == delimiter) return idx; } /* if we make it to here, we've hit the end of the string */ return -1; } /* * Returns the index of the third delimiter */ static int parse_regex_delim(const char *cmdstr, char **match, char **replace) { const char *cmdstr_ptr = cmdstr; char delimiter; int idx = 0; /* verify that the 's' or 'y' is followed by something. That something * (typically a 'slash') is now our regexp delimiter... */ if (*cmdstr == '\0') bb_error_msg_and_die(bad_format_in_subst); delimiter = *(cmdstr_ptr++); /* save the match string */ idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr); if (idx == -1) { bb_error_msg_and_die(bad_format_in_subst); } *match = copy_parsing_escapes(cmdstr_ptr, idx); /* save the replacement string */ cmdstr_ptr += idx + 1; idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr); if (idx == -1) { bb_error_msg_and_die(bad_format_in_subst); } *replace = copy_parsing_escapes(cmdstr_ptr, idx); return ((cmdstr_ptr - cmdstr) + idx); } /* * returns the index in the string just past where the address ends. */ static int get_address(char *my_str, int *linenum, regex_t ** regex) { char *pos = my_str; if (isdigit(*my_str)) { *linenum = strtol(my_str, &pos, 10); /* endstr shouldnt ever equal NULL */ } else if (*my_str == '$') { *linenum = -1; pos++; } else if (*my_str == '/' || *my_str == '\\') { int next; char delimiter; char *temp; if (*my_str == '\\') delimiter = *(++pos); else delimiter = '/'; next = index_of_next_unescaped_regexp_delim(delimiter, ++pos); if (next == -1) bb_error_msg_and_die("unterminated match expression"); temp=copy_parsing_escapes(pos,next); *regex = (regex_t *) xmalloc(sizeof(regex_t)); xregcomp(*regex, temp, regex_type|REG_NEWLINE); free(temp); /* Move position to next character after last delimiter */ pos+=(next+1); } return pos - my_str; } /* Grab a filename. Whitespace at start is skipped, then goes to EOL. */ static int parse_file_cmd(sed_cmd_t * sed_cmd, const char *filecmdstr, char **retval) { int start = 0, idx, hack=0; /* Skip whitespace, then grab filename to end of line */ while (isspace(filecmdstr[start])) start++; idx=start; while(filecmdstr[idx] && filecmdstr[idx]!='\n') idx++; /* If lines glued together, put backslash back. */ if(filecmdstr[idx]=='\n') hack=1; if(idx==start) bb_error_msg_and_die("Empty filename"); *retval = bb_xstrndup(filecmdstr+start, idx-start+hack+1); if(hack) *(idx+*retval)='\\'; return idx; } static int parse_subst_cmd(sed_cmd_t * const sed_cmd, char *substr) { int cflags = regex_type; char *match; int idx = 0; /* * A substitution command should look something like this: * s/match/replace/ #gIpw * || | ||| * mandatory optional */ idx = parse_regex_delim(substr, &match, &sed_cmd->string); /* determine the number of back references in the match string */ /* Note: we compute this here rather than in the do_subst_command() * function to save processor time, at the expense of a little more memory * (4 bits) per sed_cmd */ /* process the flags */ sed_cmd->which_match=1; while (substr[++idx]) { /* Parse match number */ if(isdigit(substr[idx])) { if(match[0]!='^') { /* Match 0 treated as all, multiple matches we take the last one. */ char *pos=substr+idx; sed_cmd->which_match=(unsigned short)strtol(substr+idx,&pos,10); idx=pos-substr; } continue; } /* Skip spaces */ if(isspace(substr[idx])) continue; switch (substr[idx]) { /* Replace all occurrences */ case 'g': if (match[0] != '^') sed_cmd->which_match = 0; break; /* Print pattern space */ case 'p': sed_cmd->sub_p = 1; break; /* Write to file */ case 'w': { char *temp; idx+=parse_file_cmd(sed_cmd,substr+idx,&temp); break; } /* Ignore case (gnu exension) */ case 'I': cflags |= REG_ICASE; break; /* Comment */ case '#': while(substr[++idx]); /* Fall through */ /* End of command */ case ';': case '}': goto out; default: bb_error_msg_and_die("bad option in substitution expression"); } } out: /* compile the match string into a regex */ if (*match != '\0') { /* If match is empty, we use last regex used at runtime */ sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t)); xregcomp(sed_cmd->sub_match, match, cflags); } free(match); return idx; } /* * Process the commands arguments */ static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr) { /* handle (s)ubstitution command */ if (sed_cmd->cmd == 's') cmdstr += parse_subst_cmd(sed_cmd, cmdstr); /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */ else if (strchr("aic", sed_cmd->cmd)) { if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c') bb_error_msg_and_die ("only a beginning address can be specified for edit commands"); for(;;) { if(*cmdstr=='\n' || *cmdstr=='\\') { cmdstr++; break; } else if(isspace(*cmdstr)) cmdstr++; else break; } sed_cmd->string = bb_xstrdup(cmdstr); parse_escapes(sed_cmd->string,sed_cmd->string,strlen(cmdstr),0,0); cmdstr += strlen(cmdstr); /* handle file cmds: (r)ead */ } else if(strchr("rw", sed_cmd->cmd)) { if (sed_cmd->end_line || sed_cmd->end_match) bb_error_msg_and_die("Command only uses one address"); cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string); if(sed_cmd->cmd=='w') sed_cmd->file=bb_xfopen(sed_cmd->string,"w"); /* handle branch commands */ } else if (strchr(":btT", sed_cmd->cmd)) { int length; while(isspace(*cmdstr)) cmdstr++; length = strcspn(cmdstr, semicolon_whitespace); if (length) { sed_cmd->string = strndup(cmdstr, length); cmdstr += length; } } /* translation command */ else if (sed_cmd->cmd == 'y') { char *match, *replace; int i=cmdstr[0]; cmdstr+=parse_regex_delim(cmdstr, &match, &replace)+1; /* \n already parsed, but \delimiter needs unescaping. */ parse_escapes(match,match,strlen(match),i,i); parse_escapes(replace,replace,strlen(replace),i,i); sed_cmd->string = xcalloc(1, (strlen(match) + 1) * 2); for (i = 0; match[i] && replace[i]; i++) { sed_cmd->string[i * 2] = match[i]; sed_cmd->string[(i * 2) + 1] = replace[i]; } free(match); free(replace); } /* if it wasnt a single-letter command that takes no arguments * then it must be an invalid command. */ else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) { bb_error_msg_and_die("Unsupported command %c", sed_cmd->cmd); } /* give back whatever's left over */ return (cmdstr); } /* Parse address+command sets, skipping comment lines. */ static void add_cmd(char *cmdstr) { static char *add_cmd_line=NULL; sed_cmd_t *sed_cmd; int temp; /* Append this line to any unfinished line from last time. */ if(add_cmd_line) { int lastlen=strlen(add_cmd_line); char *tmp=xmalloc(lastlen+strlen(cmdstr)+2); memcpy(tmp,add_cmd_line,lastlen); tmp[lastlen]='\n'; strcpy(tmp+lastlen+1,cmdstr); free(add_cmd_line); cmdstr=add_cmd_line=tmp; } else add_cmd_line=NULL; /* If this line ends with backslash, request next line. */ temp=strlen(cmdstr); if(temp && cmdstr[temp-1]=='\\') { if(!add_cmd_line) add_cmd_line=strdup(cmdstr); add_cmd_line[temp-1]=0; return; } /* Loop parsing all commands in this line. */ while(*cmdstr) { /* Skip leading whitespace and semicolons */ cmdstr += strspn(cmdstr, semicolon_whitespace); /* If no more commands, exit. */ if(!*cmdstr) break; /* if this is a comment, jump past it and keep going */ if (*cmdstr == '#') { /* "#n" is the same as using -n on the command line */ if (cmdstr[1] == 'n') be_quiet++; if(!(cmdstr=strpbrk(cmdstr, "\n\r"))) break; continue; } /* parse the command * format is: [addr][,addr][!]cmd * |----||-----||-| * part1 part2 part3 */ sed_cmd = xcalloc(1, sizeof(sed_cmd_t)); /* first part (if present) is an address: either a '$', a number or a /regex/ */ cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match); /* second part (if present) will begin with a comma */ if (*cmdstr == ',') { int idx; cmdstr++; idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match); if (!idx) bb_error_msg_and_die("get_address: no address found in string\n"); cmdstr += idx; } /* skip whitespace before the command */ while (isspace(*cmdstr)) cmdstr++; /* Check for inversion flag */ if (*cmdstr == '!') { sed_cmd->invert = 1; cmdstr++; /* skip whitespace before the command */ while (isspace(*cmdstr)) cmdstr++; } /* last part (mandatory) will be a command */ if (!*cmdstr) bb_error_msg_and_die("missing command"); sed_cmd->cmd = *(cmdstr++); cmdstr = parse_cmd_args(sed_cmd, cmdstr); /* Add the command to the command array */ sed_cmd_tail->next = sed_cmd; sed_cmd_tail = sed_cmd_tail->next; } /* If we glued multiple lines together, free the memory. */ if(add_cmd_line) { free(add_cmd_line); add_cmd_line=NULL; } } /* Append to a string, reallocating memory as necessary. */ static struct pipeline { char *buf; /* Space to hold string */ int idx; /* Space used */ int len; /* Space allocated */ } pipeline; #define PIPE_GROW 64 static void pipe_putc(char c) { if(pipeline.idx==pipeline.len) { pipeline.buf = xrealloc(pipeline.buf, pipeline.len + PIPE_GROW); pipeline.len+=PIPE_GROW; } pipeline.buf[pipeline.idx++] = (c); } static void do_subst_w_backrefs(const char *line, const char *replace) { int i,j; /* go through the replacement string */ for (i = 0; replace[i]; i++) { /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */ if (replace[i] == '\\' && replace[i+1]>='0' && replace[i+1]<='9') { int backref=replace[++i]-'0'; /* print out the text held in regmatch[backref] */ if(regmatch[backref].rm_so != -1) for (j = regmatch[backref].rm_so; j < regmatch[backref].rm_eo; j++) pipe_putc(line[j]); } /* if we find a backslash escaped character, print the character */ else if (replace[i] == '\\') pipe_putc(replace[++i]); /* if we find an unescaped '&' print out the whole matched text. */ else if (replace[i] == '&') for (j = regmatch[0].rm_so; j < regmatch[0].rm_eo; j++) pipe_putc(line[j]); /* Otherwise just output the character. */ else pipe_putc(replace[i]); } } static int do_subst_command(sed_cmd_t * sed_cmd, char **line) { char *oldline = *line; int altered = 0; int match_count=0; regex_t *current_regex; /* Handle empty regex. */ if (sed_cmd->sub_match == NULL) { current_regex = previous_regex_ptr; if(!current_regex) bb_error_msg_and_die("No previous regexp."); } else previous_regex_ptr = current_regex = sed_cmd->sub_match; /* Find the first match */ if(REG_NOMATCH==regexec(current_regex, oldline, 10, regmatch, 0)) return 0; /* Initialize temporary output buffer. */ pipeline.buf=xmalloc(PIPE_GROW); pipeline.len=PIPE_GROW; pipeline.idx=0; /* Now loop through, substituting for matches */ do { int i; /* Work around bug in glibc regexec, demonstrated by: echo " a.b" | busybox sed 's [^ .]* x g' The match_count check is so not to break echo "hi" | busybox sed 's/^/!/g' */ if(!regmatch[0].rm_so && !regmatch[0].rm_eo && match_count) { pipe_putc(*(oldline++)); continue; } match_count++; /* If we aren't interested in this match, output old line to end of match and continue */ if(sed_cmd->which_match && sed_cmd->which_match!=match_count) { for(i=0;i<regmatch[0].rm_eo;i++) pipe_putc(oldline[i]); continue; } /* print everything before the match */ for (i = 0; i < regmatch[0].rm_so; i++) pipe_putc(oldline[i]); /* then print the substitution string */ do_subst_w_backrefs(oldline, sed_cmd->string); /* advance past the match */ oldline += regmatch[0].rm_eo; /* flag that something has changed */ altered++; /* if we're not doing this globally, get out now */ if (sed_cmd->which_match) break; } while (*oldline && (regexec(current_regex, oldline, 10, regmatch, 0) != REG_NOMATCH)); /* Copy rest of string into output pipeline */ while(*oldline) pipe_putc(*(oldline++)); pipe_putc(0); free(*line); *line = pipeline.buf; return altered; } /* Set command pointer to point to this label. (Does not handle null label.) */ static sed_cmd_t *branch_to(const char *label) { sed_cmd_t *sed_cmd; for (sed_cmd = sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { if ((sed_cmd->cmd == ':') && (sed_cmd->string) && (strcmp(sed_cmd->string, label) == 0)) { return (sed_cmd); } } bb_error_msg_and_die("Can't find label for jump to `%s'", label); } /* Append copy of string to append buffer */ static void append(char *s) { struct append_list *temp=calloc(1,sizeof(struct append_list)); if(append_head) append_tail=(append_tail->next=temp); else append_head=append_tail=temp; temp->string=strdup(s); } static void flush_append(void) { /* Output appended lines. */ while(append_head) { fprintf(nonstdout,"%s\n",append_head->string); append_tail=append_head->next; free(append_head->string); free(append_head); append_head=append_tail; } append_head=append_tail=NULL; } static void add_input_file(FILE *file) { input_file_list=xrealloc(input_file_list,(input_file_count+1)*sizeof(FILE *)); input_file_list[input_file_count++]=file; } /* Get next line of input from input_file_list, flushing append buffer and * noting if we ran out of files without a newline on the last line we read. */ static char *get_next_line(int *no_newline) { char *temp=NULL; int len; flush_append(); while(current_input_file<input_file_count) { temp=bb_get_line_from_file(input_file_list[current_input_file]); if(temp) { len=strlen(temp); *no_newline=!(len && temp[len-1]=='\n'); if(!*no_newline) temp[len-1]=0; break; } else fclose(input_file_list[current_input_file++]); } return temp; } /* Output line of text. missing_newline means the last line output did not end with a newline. no_newline means this line does not end with a newline. */ static int puts_maybe_newline(char *s, FILE *file, int missing_newline, int no_newline) { if(missing_newline) fputc('\n',file); fputs(s,file); if(!no_newline) fputc('\n',file); if(ferror(file)) { fprintf(stderr,"Write failed.\n"); exit(4); /* It's what gnu sed exits with... */ } return no_newline; } #define sed_puts(s,n) missing_newline=puts_maybe_newline(s,nonstdout,missing_newline,n) static void process_files(void) { char *pattern_space, *next_line; int linenum = 0, missing_newline=0; int no_newline,next_no_newline=0; next_line = get_next_line(&next_no_newline); /* go through every line in each file */ for(;;) { sed_cmd_t *sed_cmd; int substituted=0; /* Advance to next line. Stop if out of lines. */ if(!(pattern_space=next_line)) break; no_newline=next_no_newline; /* Read one line in advance so we can act on the last line, the '$' address */ next_line = get_next_line(&next_no_newline); linenum++; restart: /* for every line, go through all the commands */ for (sed_cmd = sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { int old_matched, matched; old_matched = sed_cmd->in_match; /* Determine if this command matches this line: */ /* Are we continuing a previous multi-line match? */ sed_cmd->in_match = sed_cmd->in_match /* Or is no range necessary? */ || (!sed_cmd->beg_line && !sed_cmd->end_line && !sed_cmd->beg_match && !sed_cmd->end_match) /* Or did we match the start of a numerical range? */ || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) /* Or does this line match our begin address regex? */ || (sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0)) /* Or did we match last line of input? */ || (sed_cmd->beg_line == -1 && next_line == NULL); /* Snapshot the value */ matched = sed_cmd->in_match; /* Is this line the end of the current match? */ if(matched) { sed_cmd->in_match = !( /* has the ending line come, or is this a single address command? */ (sed_cmd->end_line ? sed_cmd->end_line==-1 ? !next_line : sed_cmd->end_line<=linenum : !sed_cmd->end_match) /* or does this line matches our last address regex */ || (sed_cmd->end_match && old_matched && (regexec(sed_cmd->end_match, pattern_space, 0, NULL, 0) == 0)) ); } /* Skip blocks of commands we didn't match. */ if (sed_cmd->cmd == '{') { if(sed_cmd->invert ? matched : !matched) while(sed_cmd && sed_cmd->cmd!='}') sed_cmd=sed_cmd->next; if(!sed_cmd) bb_error_msg_and_die("Unterminated {"); continue; } /* Okay, so did this line match? */ if (sed_cmd->invert ? !matched : matched) { /* Update last used regex in case a blank substitute BRE is found */ if (sed_cmd->beg_match) { previous_regex_ptr = sed_cmd->beg_match; } /* actual sedding */ switch (sed_cmd->cmd) { /* Print line number */ case '=': fprintf(nonstdout,"%d\n", linenum); break; /* Write the current pattern space up to the first newline */ case 'P': { char *tmp = strchr(pattern_space, '\n'); if (tmp) { *tmp = '\0'; sed_puts(pattern_space,1); *tmp = '\n'; break; } /* Fall Through */ } /* Write the current pattern space to output */ case 'p': sed_puts(pattern_space,no_newline); break; /* Delete up through first newline */ case 'D': { char *tmp = strchr(pattern_space,'\n'); if(tmp) { tmp=bb_xstrdup(tmp+1); free(pattern_space); pattern_space=tmp; goto restart; } } /* discard this line. */ case 'd': goto discard_line; /* Substitute with regex */ case 's': if(do_subst_command(sed_cmd, &pattern_space)) { substituted|=1; /* handle p option */ if(sed_cmd->sub_p) sed_puts(pattern_space,no_newline); /* handle w option */ if(sed_cmd->file) sed_cmd->no_newline=puts_maybe_newline(pattern_space, sed_cmd->file, sed_cmd->no_newline, no_newline); } break; /* Append line to linked list to be printed later */ case 'a': { append(sed_cmd->string); break; } /* Insert text before this line */ case 'i': sed_puts(sed_cmd->string,1); break; /* Cut and paste text (replace) */ case 'c': /* Only triggers on last line of a matching range. */ if (!sed_cmd->in_match) sed_puts(sed_cmd->string,0); goto discard_line; /* Read file, append contents to output */ case 'r': { FILE *outfile; outfile = fopen(sed_cmd->string, "r"); if (outfile) { char *line; while ((line = bb_get_chomped_line_from_file(outfile)) != NULL) append(line); bb_xprint_and_close_file(outfile); } break; } /* Write pattern space to file. */ case 'w': sed_cmd->no_newline=puts_maybe_newline(pattern_space,sed_cmd->file, sed_cmd->no_newline,no_newline); break; /* Read next line from input */ case 'n': if (!be_quiet) sed_puts(pattern_space,no_newline); if (next_line) { free(pattern_space); pattern_space = next_line; no_newline=next_no_newline; next_line = get_next_line(&next_no_newline); linenum++; break; } /* fall through */ /* Quit. End of script, end of input. */ case 'q': /* Exit the outer while loop */ free(next_line); next_line = NULL; goto discard_commands; /* Append the next line to the current line */ case 'N': { /* If no next line, jump to end of script and exit. */ if (next_line == NULL) { /* Jump to end of script and exit */ free(next_line); next_line = NULL; goto discard_line; /* append next_line, read new next_line. */ } else { int len=strlen(pattern_space); pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); pattern_space[len]='\n'; strcpy(pattern_space+len+1, next_line); no_newline=next_no_newline; next_line = get_next_line(&next_no_newline); linenum++; } break; } /* Test/branch if substitution occurred */ case 't': if(!substituted) break; substituted=0; /* Fall through */ /* Test/branch if substitution didn't occur */ case 'T': if (substituted) break; /* Fall through */ /* Branch to label */ case 'b': if (!sed_cmd->string) goto discard_commands; else sed_cmd = branch_to(sed_cmd->string); break; /* Transliterate characters */ case 'y': { int i; for (i = 0; pattern_space[i]; i++) { int j; for (j = 0; sed_cmd->string[j]; j += 2) { if (pattern_space[i] == sed_cmd->string[j]) { pattern_space[i] = sed_cmd->string[j + 1]; break; } } } break; } case 'g': /* Replace pattern space with hold space */ free(pattern_space); pattern_space = strdup(hold_space ? hold_space : ""); break; case 'G': /* Append newline and hold space to pattern space */ { int pattern_space_size = 2; int hold_space_size = 0; if (pattern_space) pattern_space_size += strlen(pattern_space); if (hold_space) hold_space_size = strlen(hold_space); pattern_space = xrealloc(pattern_space, pattern_space_size + hold_space_size); if (pattern_space_size == 2) pattern_space[0]=0; strcat(pattern_space, "\n"); if (hold_space) strcat(pattern_space, hold_space); no_newline=0; break; } case 'h': /* Replace hold space with pattern space */ free(hold_space); hold_space = strdup(pattern_space); break; case 'H': /* Append newline and pattern space to hold space */ { int hold_space_size = 2; int pattern_space_size = 0; if (hold_space) hold_space_size += strlen(hold_space); if (pattern_space) pattern_space_size = strlen(pattern_space); hold_space = xrealloc(hold_space, hold_space_size + pattern_space_size); if (hold_space_size == 2) hold_space[0]=0; strcat(hold_space, "\n"); if (pattern_space) strcat(hold_space, pattern_space); break; } case 'x': /* Exchange hold and pattern space */ { char *tmp = pattern_space; pattern_space = hold_space; no_newline=0; hold_space = tmp; break; } } } } /* * exit point from sedding... */ discard_commands: /* we will print the line unless we were told to be quiet ('-n') or if the line was suppressed (ala 'd'elete) */ if (!be_quiet) sed_puts(pattern_space,no_newline); /* Delete and such jump here. */ discard_line: flush_append(); free(pattern_space); } } /* It is possible to have a command line argument with embedded newlines. This counts as multiple command lines. */ static void add_cmd_block(char *cmdstr) { int go=1; char *temp=bb_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); } extern int sed_main(int argc, char **argv) { int status = EXIT_SUCCESS, opt, getpat = 1; #ifdef CONFIG_FEATURE_CLEAN_UP /* destroy command strings on exit */ if (atexit(free_and_close_stuff) == -1) bb_perror_msg_and_die("atexit"); #endif /* Lie to autoconf when it starts asking stupid questions. */ if(argc==2 && !strcmp(argv[1],"--version")) { printf("This is not GNU sed version 4.0\n"); exit(0); } /* do normal option parsing */ while ((opt = getopt(argc, argv, "irne:f:")) > 0) { switch (opt) { case 'i': in_place++; atexit(cleanup_outname); break; case 'r': regex_type|=REG_EXTENDED; break; case 'n': be_quiet++; break; case 'e': add_cmd_block(optarg); getpat=0; break; case 'f': { FILE *cmdfile; char *line; cmdfile = bb_xfopen(optarg, "r"); while ((line = bb_get_chomped_line_from_file(cmdfile)) != NULL) { add_cmd(line); getpat=0; free(line); } bb_xprint_and_close_file(cmdfile); break; } default: bb_show_usage(); } } /* if we didn't get a pattern from -e or -f, use argv[optind] */ if(getpat) { if (argv[optind] == NULL) bb_show_usage(); else add_cmd_block(argv[optind++]); } /* Flush any unfinished commands. */ add_cmd(""); /* By default, we write to stdout */ nonstdout=stdout; /* argv[(optind)..(argc-1)] should be names of file to process. If no * files were specified or '-' was specified, take input from stdin. * Otherwise, we process all the files specified. */ if (argv[optind] == NULL) { if(in_place) bb_error_msg_and_die("Filename required for -i"); add_input_file(stdin); process_files(); } else { int i; FILE *file; for (i = optind; i < argc; i++) { if(!strcmp(argv[i], "-") && !in_place) { add_input_file(stdin); process_files(); } else { file = bb_wfopen(argv[i], "r"); if (file) { if(in_place) { struct stat statbuf; int nonstdoutfd; outname=bb_xstrndup(argv[i],strlen(argv[i])+6); strcat(outname,"XXXXXX"); if(-1==(nonstdoutfd=mkstemp(outname))) bb_error_msg_and_die("no temp file"); nonstdout=fdopen(nonstdoutfd,"w"); /* Set permissions of output file */ fstat(fileno(file),&statbuf); fchmod(nonstdoutfd,statbuf.st_mode); add_input_file(file); process_files(); fclose(nonstdout); nonstdout=stdout; unlink(argv[i]); rename(outname,argv[i]); free(outname); outname=0; } else add_input_file(file); } else { status = EXIT_FAILURE; } } } if(input_file_count>current_input_file) process_files(); } return status; }