* resulting sed_cmd_t structures are appended to a linked list
* (G.sed_cmd_head/G.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.
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
*/
+//config:config SED
+//config: bool "sed"
+//config: default y
+//config: help
+//config: sed is used to perform text transformations on a file
+//config: or input from a pipeline.
+
+//kbuild:lib-$(CONFIG_SED) += sed.o
+
+//applet:IF_SED(APPLET(sed, BB_DIR_BIN, BB_SUID_DROP))
+
//usage:#define sed_trivial_usage
//usage: "[-inrE] [-f FILE]... [-e CMD]... [FILE]...\n"
//usage: "or: sed [-inrE] CMD [FILE]..."
struct globals {
/* options */
int be_quiet, regex_type;
+
FILE *nonstdout;
char *outname, *hold_space;
+ smallint exitcode;
- /* List of input files */
- int input_file_count, current_input_file;
- FILE **input_file_list;
+ /* list of input files */
+ int current_input_file, last_input_file;
+ char **input_file_list;
+ FILE *current_fp;
regmatch_t regmatch[10];
regex_t *previous_regex_ptr;
/* linked list of sed commands */
sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
- /* Linked list of append lines */
+ /* linked list of append lines */
llist_t *append_head;
char *add_cmd_line;
free(G.hold_space);
- while (G.current_input_file < G.input_file_count)
- fclose(G.input_file_list[G.current_input_file++]);
+ if (G.current_fp)
+ fclose(G.current_fp);
}
#else
void sed_free_and_close_stuff(void);
/*
* A substitution command should look something like this:
- * s/match/replace/ #gIpw
+ * s/match/replace/ #giIpw
* || | |||
* mandatory optional
*/
break;
}
/* Ignore case (gnu exension) */
+ case 'i':
case 'I':
cflags |= REG_ICASE;
break;
/* will be returned if last line in the file
* doesn't end with either '\n' or '\0' */
gc = NO_EOL_CHAR;
- while (G.current_input_file < G.input_file_count) {
- FILE *fp = G.input_file_list[G.current_input_file];
+ for (; G.current_input_file <= G.last_input_file; G.current_input_file++) {
+ FILE *fp = G.current_fp;
+ if (!fp) {
+ const char *path = G.input_file_list[G.current_input_file];
+ fp = stdin;
+ if (path != bb_msg_standard_input) {
+ fp = fopen_or_warn(path, "r");
+ if (!fp) {
+ G.exitcode = EXIT_FAILURE;
+ continue;
+ }
+ }
+ G.current_fp = fp;
+ }
/* Read line up to a newline or NUL byte, inclusive,
* return malloc'ed char[]. length of the chunk read
* is stored in len. NULL if EOF/error */
* (note: *no* newline after "b bang"!) */
}
/* Close this file and advance to next one */
- fclose(fp);
- G.current_input_file++;
+ fclose_if_not_stdin(fp);
+ G.current_fp = NULL;
}
*gets_char = gc;
return temp;
free(sv);
}
-static void add_input_file(FILE *file)
-{
- G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
- G.input_file_list[G.input_file_count++] = file;
-}
-
int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int sed_main(int argc UNUSED_PARAM, char **argv)
{
"file\0" Required_argument "f";
#endif
- int status = EXIT_SUCCESS;
-
INIT_G();
/* destroy command strings on exit */
/* argv[0..(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[0] == NULL) {
+ G.input_file_list = argv;
+ if (!argv[0]) {
if (opt & OPT_in_place)
bb_error_msg_and_die(bb_msg_requires_arg, "-i");
- add_input_file(stdin);
+ argv[0] = (char*)bb_msg_standard_input;
+ /* G.last_input_file = 0; - already is */
} else {
- int i;
+ goto start;
- for (i = 0; argv[i]; i++) {
+ for (; *argv; argv++) {
struct stat statbuf;
int nonstdoutfd;
- FILE *file;
sed_cmd_t *sed_cmd;
- if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
- add_input_file(stdin);
- process_files();
- continue;
- }
- file = fopen_or_warn(argv[i], "r");
- if (!file) {
- status = EXIT_FAILURE;
- continue;
- }
- add_input_file(file);
+ G.last_input_file++;
+ start:
if (!(opt & OPT_in_place)) {
+ if (LONE_DASH(*argv)) {
+ *argv = (char*)bb_msg_standard_input;
+ process_files();
+ }
continue;
}
/* -i: process each FILE separately: */
- G.outname = xasprintf("%sXXXXXX", argv[i]);
+ G.outname = xasprintf("%sXXXXXX", *argv);
nonstdoutfd = xmkstemp(G.outname);
G.nonstdout = xfdopen_for_write(nonstdoutfd);
/* Set permissions/owner of output file */
- fstat(fileno(file), &statbuf);
+ stat(*argv, &statbuf);
/* chmod'ing AFTER chown would preserve suid/sgid bits,
* but GNU sed 4.2.1 does not preserve them either */
fchmod(nonstdoutfd, statbuf.st_mode);
G.nonstdout = stdout;
if (opt_i) {
- char *backupname = xasprintf("%s%s", argv[i], opt_i);
- xrename(argv[i], backupname);
+ char *backupname = xasprintf("%s%s", *argv, opt_i);
+ xrename(*argv, backupname);
free(backupname);
}
- /* else unlink(argv[i]); - rename below does this */
- xrename(G.outname, argv[i]); //TODO: rollback backup on error?
+ /* else unlink(*argv); - rename below does this */
+ xrename(G.outname, *argv); //TODO: rollback backup on error?
free(G.outname);
G.outname = NULL;
}
}
/* Here, to handle "sed 'cmds' nonexistent_file" case we did:
- * if (G.current_input_file >= G.input_file_count)
- * return status;
+ * if (G.current_input_file[G.current_input_file] == NULL)
+ * return G.exitcode;
* but it's not needed since process_files() works correctly
* in this case too. */
}
+
process_files();
- return status;
+ return G.exitcode;
}