Some more patchelttes from Larry Doolittle.
[oweals/busybox.git] / sed.c
diff --git a/sed.c b/sed.c
index 1342a66433554a7bf9dce8fb02cfaee22ac2427f..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 */
@@ -175,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 {
@@ -351,6 +357,45 @@ out:
        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;
@@ -361,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);
@@ -373,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];
@@ -525,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);
@@ -542,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':
@@ -567,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)
@@ -585,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;
@@ -598,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;
@@ -614,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;
 
@@ -660,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);
        }