Some more patchelttes from Larry Doolittle.
[oweals/busybox.git] / sed.c
diff --git a/sed.c b/sed.c
index 3901813754bd3d832f14ce6a5bf1d2be7e469286..47fb63712066a5f1a8e02133d7b9f8bbe06006f5 100644 (file)
--- a/sed.c
+++ b/sed.c
@@ -27,6 +27,7 @@
         - 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 (\1, \2...\9)
         
         (Note: Specifying an address (range) to match is *optional*; commands
@@ -90,6 +91,11 @@ struct sed_cmd {
        /* EDIT COMMAND (a,i,c) SPEICIFIC FIELDS */
 
        char *editline;
+
+
+       /* FILE COMMAND (r) SPEICIFIC FIELDS */
+
+       char *filename;
 };
 
 /* globals */
@@ -154,6 +160,9 @@ static int get_address(struct sed_cmd *sed_cmd, const char *str, int *line, rege
 {
        char *my_str = strdup(str);
        int idx = 0;
+       char olddelimiter;
+       olddelimiter = sed_cmd->delimiter;
+       sed_cmd->delimiter = '/';
 
        if (isdigit(my_str[idx])) {
                do {
@@ -172,7 +181,7 @@ static int get_address(struct sed_cmd *sed_cmd, const char *str, int *line, rege
                        error_msg_and_die("unterminated match expression");
                my_str[idx] = '\0';
                *regex = (regex_t *)xmalloc(sizeof(regex_t));
-               xregcomp(*regex, my_str+1, 0);
+               xregcomp(*regex, my_str+1, REG_NEWLINE);
                idx++; /* so it points to the next character after the last '/' */
        }
        else {
@@ -182,6 +191,7 @@ static int get_address(struct sed_cmd *sed_cmd, const char *str, int *line, rege
        }
 
        free(my_str);
+       sed_cmd->delimiter = olddelimiter;
        return idx;
 }
 
@@ -313,7 +323,7 @@ static int parse_edit_cmd(struct sed_cmd *sed_cmd, const char *editstr)
 
        /* now we need to go through * and: s/\\[\r\n]$/\n/g on the edit line */
        while (ptr[idx]) {
-               while (ptr[idx] != '\\' && (ptr[idx+1] != '\n' || ptr[idx+1] != '\r')) {
+               while (ptr[idx] != '\\' || (ptr[idx+1] != '\n' && ptr[idx+1] != '\r')) {
                        idx++;
                        if (!ptr[idx]) {
                                goto out;
@@ -329,22 +339,63 @@ static int parse_edit_cmd(struct sed_cmd *sed_cmd, const char *editstr)
        }
 
 out:
-       ptr[idx] = '\n';
-       ptr[idx+1] = 0;
-
        /* this accounts for discrepancies between the modified string and the
         * original string passed in to this function */
        idx += slashes_eaten;
 
-       /* this accounts for the fact that A) we started at index 3, not at index
-        * 0  and B) that we added an extra '\n' at the end (if you think the next
-        * line should read 'idx += 4' remember, arrays are zero-based) */
+       /* figure out if we need to add a newline */
+       if (ptr[idx-1] != '\n') {
+               ptr[idx] = '\n';
+               idx++;
+       }
 
-       idx += 3;
+       /* terminate string */
+       ptr[idx]= 0;
+       /* adjust for opening 2 chars [aic]\ */
+       idx += 2;
 
        return idx;
 }
 
+
+static int parse_file_cmd(struct sed_cmd *sed_cmd, const char *filecmdstr)
+{
+       int idx = 0;
+       int filenamelen = 0;
+
+       /*
+        * the string that gets passed to this function should look like this:
+        *    '[ ]filename'
+        *      |  |
+        *      |  a filename
+        *      |
+        *     optional whitespace
+
+        *   re: the file to be read, the GNU manual says the following: "Note that
+        *   if filename cannot be read, it is treated as if it were an empty file,
+        *   without any error indication." Thus, all of the following commands are
+        *   perfectly leagal:
+        *
+        *   sed -e '1r noexist'
+        *   sed -e '1r ;'
+        *   sed -e '1r'
+        */
+
+       /* the file command may be followed by whitespace; move past it. */
+       while (isspace(filecmdstr[++idx]))
+               { ; }
+               
+       /* the first non-whitespace we get is a filename. the filename ends when we
+        * hit a normal sed command terminator or end of string */
+       filenamelen = strcspn(&filecmdstr[idx], "; \n\r\t\v\0");
+       sed_cmd->filename = xmalloc(sizeof(char) * filenamelen + 1);
+       strncpy(sed_cmd->filename, &filecmdstr[idx], filenamelen);
+       sed_cmd->filename[filenamelen] = 0;
+
+       return idx + filenamelen;
+}
+
+
 static char *parse_cmd_str(struct sed_cmd *sed_cmd, const char *cmdstr)
 {
        int idx = 0;
@@ -355,7 +406,6 @@ static char *parse_cmd_str(struct sed_cmd *sed_cmd, const char *cmdstr)
         *            part1 part2  part3
         */
 
-
        /* first part (if present) is an address: either a number or a /regex/ */
        if (isdigit(cmdstr[idx]) || cmdstr[idx] == '/')
                idx = get_address(sed_cmd, cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
@@ -367,24 +417,32 @@ static char *parse_cmd_str(struct sed_cmd *sed_cmd, const char *cmdstr)
        /* last part (mandatory) will be a command */
        if (cmdstr[idx] == '\0')
                error_msg_and_die("missing command");
-       if (!strchr("pdsaic", cmdstr[idx])) /* <-- XXX add new commands here */
-               error_msg_and_die("invalid command");
        sed_cmd->cmd = cmdstr[idx];
 
-       /* special-case handling for (s)ubstitution */
-       if (sed_cmd->cmd == 's') {
+       /* if it was a single-letter command that takes no arguments (such as 'p'
+        * or 'd') all we need to do is increment the index past that command */
+       if (strchr("pd", cmdstr[idx])) {
+               idx++;
+       }
+       /*  handle (s)ubstitution */
+       else if (sed_cmd->cmd == 's') {
                idx += parse_subst_cmd(sed_cmd, &cmdstr[idx]);
        }
-       /* special-case handling for (a)ppend, (i)nsert, and (c)hange */
+       /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
        else if (strchr("aic", cmdstr[idx])) {
                if (sed_cmd->end_line || sed_cmd->end_match)
                        error_msg_and_die("only a beginning address can be specified for edit commands");
                idx += parse_edit_cmd(sed_cmd, &cmdstr[idx]);
        }
-       /* if it was a single-letter command (such as 'p' or 'd') we need to
-        * increment the index past that command */
-       else
-               idx++;
+       /* handle file cmds: (r)ead */
+       else if (sed_cmd->cmd == 'r') {
+               if (sed_cmd->end_line || sed_cmd->end_match)
+                       error_msg_and_die("Command only uses one address");
+               idx += parse_file_cmd(sed_cmd, &cmdstr[idx]);
+       }
+       else {
+               error_msg_and_die("invalid command");
+       }
 
        /* give back whatever's left over */
        return (char *)&cmdstr[idx];
@@ -519,9 +577,7 @@ static int do_subst_command(const struct sed_cmd *sed_cmd, const char *line)
                        break;
        }
 
-       /* if there's anything left of the line, print it */
-       if (*hackline)
-               fputs(hackline, stdout);
+       puts(hackline);
 
        /* cleanup */
        free(regmatch);
@@ -536,7 +592,7 @@ static int do_sed_command(const struct sed_cmd *sed_cmd, const char *line)
        switch (sed_cmd->cmd) {
 
                case 'p':
-                       fputs(line, stdout);
+                       puts(line);
                        break;
 
                case 'd':
@@ -561,7 +617,7 @@ static int do_sed_command(const struct sed_cmd *sed_cmd, const char *line)
                         *    flag exists in the first place.
                         */
 
-                       /* if the user specified that they didn't want anything printed (i.e. a -n
+                       /* if the user specified that they didn't want anything printed (i.e., a -n
                         * flag and no 'p' flag after the s///), then there's really no point doing
                         * anything here. */
                        if (be_quiet && !sed_cmd->sub_p)
@@ -579,7 +635,7 @@ static int do_sed_command(const struct sed_cmd *sed_cmd, const char *line)
                        break;
 
                case 'a':
-                       fputs(line, stdout);
+                       puts(line);
                        fputs(sed_cmd->editline, stdout);
                        altered++;
                        break;
@@ -592,6 +648,17 @@ static int do_sed_command(const struct sed_cmd *sed_cmd, const char *line)
                        fputs(sed_cmd->editline, stdout);
                        altered++;
                        break;
+
+               case 'r': {
+                       FILE *file;
+                       puts(line);
+                       file = fopen(sed_cmd->filename, "r");
+                       if (file)
+                               print_file(file);
+                       /* else if we couldn't open the file, no biggie, just don't print anything */
+                       altered++;
+                       }
+                       break;
        }
 
        return altered;
@@ -608,6 +675,7 @@ static void process_file(FILE *file)
        /* go through every line in the file */
        while ((line = get_line_from_file(file)) != NULL) {
 
+               chomp(line);
                linenum++;
                line_altered = 0;
 
@@ -618,9 +686,10 @@ static void process_file(FILE *file)
                        if (sed_cmds[i].beg_match && sed_cmds[i].end_match) {
                                if (still_in_range || regexec(sed_cmds[i].beg_match, line, 0, NULL, 0) == 0) {
                                        line_altered += do_sed_command(&sed_cmds[i], line);
-                                       still_in_range = 1; 
-                                       if (regexec(sed_cmds[i].end_match, line, 0, NULL, 0) == 0)
+                                       if (still_in_range && regexec(sed_cmds[i].end_match, line, 0, NULL, 0) == 0)
                                                still_in_range = 0;
+                                       else
+                                               still_in_range = 1;
                                }
                        }
 
@@ -653,7 +722,7 @@ static void process_file(FILE *file)
                 * line was altered (via a 'd'elete or 's'ubstitution), in which case
                 * the altered line was already printed */
                if (!be_quiet && !line_altered)
-                       fputs(line, stdout);
+                       puts(line);
 
                free(line);
        }
@@ -670,11 +739,8 @@ extern int sed_main(int argc, char **argv)
 #endif
 
        /* do normal option parsing */
-       while ((opt = getopt(argc, argv, "hne:f:")) > 0) {
+       while ((opt = getopt(argc, argv, "ne:f:")) > 0) {
                switch (opt) {
-                       case 'h':
-                               show_usage();
-                               break;
                        case 'n':
                                be_quiet++;
                                break;
@@ -684,6 +750,8 @@ extern int sed_main(int argc, char **argv)
                        case 'f': 
                                load_cmd_file(optarg);
                                break;
+                       default:
+                               show_usage();
                }
        }