Hi, Here's a diff to add the '-i' flag to sed to do inplace edits. It's mostly from FreeBSD with some adjustments to prevent a race with unlink() and fopen() during the tempfile creation.
It's been tested in a full ports bulk (thanks aja), and went through a build of base and xenocara. Regress tests will also be added for this. This diff is already OK millert@. Any more OKs? Index: defs.h =================================================================== RCS file: /cvs/src/usr.bin/sed/defs.h,v retrieving revision 1.5 diff -u -p -r1.5 defs.h --- defs.h 19 Jan 2015 15:30:52 -0000 1.5 +++ defs.h 16 Jul 2015 18:45:58 -0000 @@ -128,6 +128,7 @@ typedef struct { char *space; /* Current space pointer. */ size_t len; /* Current length. */ int deleted; /* If deleted. */ + int append_newline; /* If originally terminated by \n. */ char *back; /* Backing memory. */ size_t blen; /* Backing memory length. */ } SPACE; Index: extern.h =================================================================== RCS file: /cvs/src/usr.bin/sed/extern.h,v retrieving revision 1.9 diff -u -p -r1.9 extern.h --- extern.h 13 Apr 2015 05:11:23 -0000 1.9 +++ extern.h 16 Jul 2015 00:23:57 -0000 @@ -40,17 +40,19 @@ extern regmatch_t *match; extern size_t maxnsub; extern u_long linenum; extern size_t appendnum; -extern int lastline; extern int Eflag, aflag, eflag, nflag; -extern char *fname; +extern const char *fname, *outfname; +extern FILE *infile, *outfile; void cfclose(struct s_command *, struct s_command *); void compile(void); -void cspace(SPACE *, char *, size_t, enum e_spflag); +void cspace(SPACE *, const char *, size_t, enum e_spflag); char *cu_fgets(char **, size_t *); void err(int, const char *, ...); int mf_fgets(SPACE *, enum e_spflag); +int lastline(void); void process(void); +void resetranges(void); char *strregerror(int, regex_t *); void *xmalloc(size_t); void *xreallocarray(void *, size_t, size_t); Index: main.c =================================================================== RCS file: /cvs/src/usr.bin/sed/main.c,v retrieving revision 1.18 diff -u -p -r1.18 main.c --- main.c 26 Nov 2014 18:34:51 -0000 1.18 +++ main.c 16 Jul 2015 19:21:16 -0000 @@ -34,6 +34,7 @@ */ #include <sys/types.h> +#include <sys/stat.h> #include <ctype.h> #include <errno.h> @@ -45,6 +46,7 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <libgen.h> #include "defs.h" #include "extern.h" @@ -78,15 +80,23 @@ struct s_flist { */ static struct s_flist *files, **fl_nextp = &files; +FILE *infile; /* Current input file */ +FILE *outfile; /* Current output file */ + int Eflag, aflag, eflag, nflag; +static int rval; /* Exit status */ /* * Current file and line number; line numbers restart across compilation - * units, but span across input files. + * units, but span across input files. The latter is optional if editing + * in place. */ -char *fname; /* File name. */ +const char *fname; /* File name. */ +const char *outfname; /* Output file name */ +static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ +static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ +char *inplace; /* Inplace edit file extension */ u_long linenum; -int lastline; /* TRUE on the last line of the last file */ static void add_compunit(enum e_cut, char *); static void add_file(char *); @@ -97,7 +107,8 @@ main(int argc, char *argv[]) int c, fflag; fflag = 0; - while ((c = getopt(argc, argv, "Eae:f:nru")) != -1) + inplace = NULL; + while ((c = getopt(argc, argv, "Eae:f:i::nru")) != -1) switch (c) { case 'E': case 'r': @@ -114,6 +125,9 @@ main(int argc, char *argv[]) fflag = 1; add_compunit(CU_FILE, optarg); break; + case 'i': + inplace = optarg ? optarg : ""; + break; case 'n': nflag = 1; break; @@ -123,8 +137,8 @@ main(int argc, char *argv[]) default: case '?': (void)fprintf(stderr, - "usage: sed [-aEnru] command [file ...]\n" - " sed [-aEnru] [-e command] [-f command_file] [file ...]\n"); + "usage: sed [-aEnru] [-i [extension]] command [file ...]\n" + " sed [-aEnru] [-i [extension]] [-e command] [-f command_file] [file ...]\n"); exit(1); } argc -= optind; @@ -148,7 +162,7 @@ main(int argc, char *argv[]) cfclose(prog, NULL); if (fclose(stdout)) err(FATAL, "stdout: %s", strerror(errno)); - exit (0); + exit (rval); } /* @@ -258,69 +272,128 @@ again: int mf_fgets(SPACE *sp, enum e_spflag spflag) { - static FILE *f; /* Current open file */ + struct stat sb; size_t len; char *p; - int c; + int c, fd; + static int firstfile; - if (f == NULL) - /* Advance to first non-empty file */ - for (;;) { - if (files == NULL) { - lastline = 1; - return (0); - } - if (files->fname == NULL) { - f = stdin; - fname = "stdin"; - } else { - fname = files->fname; - if ((f = fopen(fname, "r")) == NULL) - err(FATAL, "%s: %s", - fname, strerror(errno)); + if (infile == NULL) { + /* stdin? */ + if (files->fname == NULL) { + if (inplace != NULL) + err(FATAL, "-i may not be used with stdin"); + infile = stdin; + fname = "stdin"; + outfile = stdout; + outfname = "stdout"; + } + + firstfile = 1; + } + + for (;;) { + if (infile != NULL && (c = getc(infile)) != EOF) { + (void)ungetc(c, infile); + break; + } + /* If we are here then either eof or no files are open yet */ + if (infile == stdin) { + sp->len = 0; + return (0); + } + if (infile != NULL) { + fclose(infile); + if (*oldfname != '\0') { + if (rename(fname, oldfname) != 0) { + err(WARNING, "rename()"); + unlink(tmpfname); + exit(1); + } + *oldfname = '\0'; } - if ((c = getc(f)) != EOF) { - (void)ungetc(c, f); - break; + if (*tmpfname != '\0') { + if (outfile != NULL && outfile != stdout) + fclose(outfile); + outfile = NULL; + rename(tmpfname, fname); + *tmpfname = '\0'; } - (void)fclose(f); + outfname = NULL; + } + if (firstfile == 0) files = files->next; + else + firstfile = 0; + if (files == NULL) { + sp->len = 0; + return (0); + } + fname = files->fname; + if (inplace != NULL) { + if (lstat(fname, &sb) != 0) + err(1, "%s", fname); + if (!S_ISREG(sb.st_mode)) + err(FATAL, "%s: %s %s", fname, + "in-place editing only", + "works for regular files"); + if (*inplace != '\0') { + strlcpy(oldfname, fname, + sizeof(oldfname)); + len = strlcat(oldfname, inplace, + sizeof(oldfname)); + if (len > sizeof(oldfname)) + err(FATAL, "%s: name too long", fname); + } + len = snprintf(tmpfname, sizeof(tmpfname), "%s/.%s.XXXXXXXXXX", + dirname(fname), basename(fname)); + if (len >= sizeof(tmpfname)) + err(FATAL, "%s: name too long", fname); + if ((fd = mkstemp(tmpfname)) == -1) + err(FATAL, "%s", fname); + if ((outfile = fdopen(fd, "w")) == NULL) { + unlink(tmpfname); + err(FATAL, "%s", fname); + } + fchown(fileno(outfile), sb.st_uid, sb.st_gid); + fchmod(fileno(outfile), sb.st_mode & ALLPERMS); + outfname = tmpfname; + linenum = 0; + resetranges(); + } else { + outfile = stdout; + outfname = "stdout"; + } + if ((infile = fopen(fname, "r")) == NULL) { + err(WARNING, "%s", fname); + rval = 1; + continue; } - - if (lastline) { - sp->len = 0; - return (0); } /* + * We are here only when infile is open and we still have something + * to read from it. + * * Use fgetln so that we can handle essentially infinite input data. * Can't use the pointer into the stdio buffer as the process space * because the ungetc() can cause it to move. */ - p = fgetln(f, &len); - if (ferror(f)) + p = fgetln(infile, &len); + if (ferror(infile)) err(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO)); + if (len != 0 && p[len - 1] == '\n') { + sp->append_newline = 1; + len--; + } else if (!lastline()) { + sp->append_newline = 1; + } else { + sp->append_newline = 0; + } cspace(sp, p, len, spflag); linenum++; - /* Advance to next non-empty file */ - while ((c = getc(f)) == EOF) { - (void)fclose(f); - files = files->next; - if (files == NULL) { - lastline = 1; - return (1); - } - if (files->fname == NULL) { - f = stdin; - fname = "stdin"; - } else { - fname = files->fname; - if ((f = fopen(fname, "r")) == NULL) - err(FATAL, "%s: %s", fname, strerror(errno)); - } - } - (void)ungetc(c, f); + return (1); } @@ -353,4 +426,52 @@ add_file(char *s) *fl_nextp = fp; fp->fname = s; fl_nextp = &fp->next; +} + + +static int +next_files_have_lines() +{ + struct s_flist *file; + FILE *file_fd; + int ch; + + file = files; + while ((file = file->next) != NULL) { + if ((file_fd = fopen(file->fname, "r")) == NULL) + continue; + + if ((ch = getc(file_fd)) != EOF) { + /* + * This next file has content, therefore current + * file doesn't contains the last line. + */ + ungetc(ch, file_fd); + fclose(file_fd); + return (1); + } + + fclose(file_fd); + } + + return (0); +} + +int +lastline(void) +{ + int ch; + + if (feof(infile)) { + return !( + (inplace == NULL) && + next_files_have_lines()); + } + if ((ch = getc(infile)) == EOF) { + return !( + (inplace == NULL) && + next_files_have_lines()); + } + ungetc(ch, infile); + return (0); } Index: process.c =================================================================== RCS file: /cvs/src/usr.bin/sed/process.c,v retrieving revision 1.23 diff -u -p -r1.23 process.c --- process.c 18 Apr 2015 18:28:37 -0000 1.23 +++ process.c 16 Jul 2015 18:50:40 -0000 @@ -55,6 +55,7 @@ static SPACE HS, PS, SS; #define pd PS.deleted #define ps PS.space #define psl PS.len +#define psanl PS.append_newline #define hs HS.space #define hsl HS.len @@ -76,7 +77,10 @@ static regex_t *defpreg; size_t maxnsub; regmatch_t *match; -#define OUT(s) do { fwrite(s, sizeof(u_char), psl, stdout); } while (0) +#define OUT() do {\ + fwrite(ps, 1, psl, outfile);\ + if (psanl) fputc('\n', outfile);\ +} while (0) void process(void) @@ -85,6 +89,7 @@ process(void) SPACE tspace; size_t len, oldpsl; char *p; + int oldpsanl; for (linenum = 0; mf_fgets(&PS, REPLACE);) { pd = 0; @@ -118,8 +123,8 @@ redirect: case 'c': pd = 1; psl = 0; - if (cp->a2 == NULL || lastaddr) - (void)printf("%s", cp->t); + if (cp->a2 == NULL || lastaddr || lastline()) + (void)fprintf(outfile, "%s", cp->t); break; case 'd': pd = 1; @@ -128,7 +133,7 @@ redirect: if (pd) goto new; if (psl == 0 || - (p = memchr(ps, '\n', psl - 1)) == NULL) { + (p = memchr(ps, '\n', psl)) == NULL) { pd = 1; goto new; } else { @@ -140,25 +145,25 @@ redirect: cspace(&PS, hs, hsl, REPLACE); break; case 'G': - if (hs == NULL) - cspace(&HS, "\n", 1, REPLACE); + cspace(&PS, "\n", 1, 0); cspace(&PS, hs, hsl, 0); break; case 'h': cspace(&HS, ps, psl, REPLACE); break; case 'H': + cspace(&HS, "\n", 1, 0); cspace(&HS, ps, psl, 0); break; case 'i': - (void)printf("%s", cp->t); + (void)fprintf(outfile, "%s", cp->t); break; case 'l': lputs(ps); break; case 'n': if (!nflag && !pd) - OUT(ps); + OUT(); flush_appends(); if (!mf_fgets(&PS, REPLACE)) exit(0); @@ -166,33 +171,32 @@ redirect: break; case 'N': flush_appends(); - if (!mf_fgets(&PS, 0)) { - if (!nflag && !pd) - OUT(ps); + cspace(&PS, "\n", 1, 0); + if (!mf_fgets(&PS, 0)) exit(0); - } break; case 'p': if (pd) break; - OUT(ps); + OUT(); break; case 'P': if (pd) break; - if (psl != 0 && - (p = memchr(ps, '\n', psl - 1)) != NULL) { + if ((p = memchr(ps, '\n', psl)) != NULL) { oldpsl = psl; - psl = (p + 1) - ps; - OUT(ps); + oldpsanl = psanl; + psl = p - ps; + psanl = 1; + OUT(); psl = oldpsl; } else { - OUT(ps); + OUT(); } break; case 'q': if (!nflag && !pd) - OUT(ps); + OUT(); flush_appends(); exit(0); case 'r': @@ -225,34 +229,36 @@ redirect: DEFFILEMODE)) == -1) err(FATAL, "%s: %s", cp->t, strerror(errno)); - if (write(cp->u.fd, ps, psl) != psl) + if (write(cp->u.fd, ps, psl) != psl || + write(cp->u.fd, "\n", 1) != 1) err(FATAL, "%s: %s", cp->t, strerror(errno)); break; case 'x': if (hs == NULL) - cspace(&HS, "\n", 1, REPLACE); + cspace(&HS, "", 0, REPLACE); tspace = PS; PS = HS; + psanl = tspace.append_newline; HS = tspace; break; case 'y': if (pd || psl == 0) break; - for (p = ps, len = psl; --len; ++p) + for (p = ps, len = psl; len--; ++p) *p = cp->u.y[(unsigned char)*p]; break; case ':': case '}': break; case '=': - (void)printf("%lu\n", linenum); + (void)fprintf(outfile, "%lu\n", linenum); } cp = cp->next; } /* for all cp */ new: if (!nflag && !pd) - OUT(ps); + OUT(); flush_appends(); } /* for all lines */ } @@ -263,7 +269,7 @@ new: if (!nflag && !pd) */ #define MATCH(a) \ (a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \ - (a)->type == AT_LINE ? linenum == (a)->u.l : lastline + (a)->type == AT_LINE ? linenum == (a)->u.l : lastline() /* * Return TRUE if the command applies to the current line. Sets the inrange @@ -305,6 +311,19 @@ applies(struct s_command *cp) } /* + * Reset all inrange markers. + */ +void +resetranges(void) +{ + struct s_command *cp; + + for (cp = prog; cp; cp = cp->code == '{' ? cp->u.c : cp->next) + if (cp->a2) + cp->inrange = 0; +} + +/* * substitute -- * Do substitutions in the pattern space. Currently, we build a * copy of the new pattern space in the substitute space structure @@ -392,19 +411,21 @@ substitute(struct s_command *cp) */ tspace = PS; PS = SS; + psanl = tspace.append_newline; SS = tspace; SS.space = SS.back; /* Handle the 'p' flag. */ if (cp->u.s->p) - OUT(ps); + OUT(); /* Handle the 'w' flag. */ if (cp->u.s->wfile && !pd) { if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile, O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1) err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno)); - if (write(cp->u.s->wfd, ps, psl) != psl) + if (write(cp->u.s->wfd, ps, psl) != psl || + write(cp->u.s->wfd, "\n", 1) != 1) err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno)); } return (1); @@ -425,7 +446,7 @@ flush_appends(void) switch (appends[i].type) { case AP_STRING: fwrite(appends[i].s, sizeof(char), appends[i].len, - stdout); + outfile); break; case AP_FILE: /* @@ -439,12 +460,12 @@ flush_appends(void) if ((f = fopen(appends[i].s, "r")) == NULL) break; while ((count = fread(buf, sizeof(char), sizeof(buf), f))) - (void)fwrite(buf, sizeof(char), count, stdout); + (void)fwrite(buf, sizeof(char), count, outfile); (void)fclose(f); break; } - if (ferror(stdout)) - err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); + if (ferror(outfile)) + err(FATAL, "%s: %s", outfname, strerror(errno ? errno : EIO)); appendx = sdone = 0; } @@ -452,10 +473,14 @@ static void lputs(char *s) { int count; - char *escapes, *p; + const char *escapes; + char *p; struct winsize win; static int termwidth = -1; + if (outfile != stdout) + termwidth = 60; + if (termwidth == -1) { termwidth = 0; if ((p = getenv("COLUMNS"))) @@ -470,29 +495,33 @@ lputs(char *s) for (count = 0; *s; ++s) { if (count >= termwidth) { - (void)printf("\\\n"); + (void)fprintf(outfile, "\\\n"); count = 0; } if (isascii((unsigned char)*s) && isprint((unsigned char)*s) && *s != '\\') { - (void)putchar(*s); + (void)fputc(*s, outfile); count++; - } else if (*s != '\n') { + } else if (*s == '\n') { + (void)fputc('$', outfile); + (void)fputc('\n', outfile); + count = 0; + } else { escapes = "\\\a\b\f\r\t\v"; - (void)putchar('\\'); + (void)fputc('\\', outfile); if ((p = strchr(escapes, *s))) { - (void)putchar("\\abfrtv"[p - escapes]); + (void)fputc("\\abfrtv"[p - escapes], outfile); count += 2; } else { - (void)printf("%03o", *(u_char *)s); + (void)fprintf(outfile, "%03o", *(u_char *)s); count += 4; } } } - (void)putchar('$'); - (void)putchar('\n'); - if (ferror(stdout)) - err(FATAL, "stdout: %s", strerror(errno ? errno : EIO)); + (void)fputc('$', outfile); + (void)fputc('\n', outfile); + if (ferror(outfile)) + err(FATAL, "%s: %s", outfname, strerror(errno ? errno : EIO)); } static inline int @@ -507,9 +536,7 @@ regexec_e(regex_t *preg, const char *str } else defpreg = preg; - /* Set anchors, discounting trailing newline (if any). */ - if (slen > 0 && string[slen - 1] == '\n') - slen--; + /* Set anchors */ match[0].rm_so = 0; match[0].rm_eo = slen; @@ -575,7 +602,7 @@ regsub(SPACE *sp, char *string, char *sr * space as necessary. */ void -cspace(SPACE *sp, char *p, size_t len, enum e_spflag spflag) +cspace(SPACE *sp, const char *p, size_t len, enum e_spflag spflag) { size_t tlen; Index: sed.1 =================================================================== RCS file: /cvs/src/usr.bin/sed/sed.1,v retrieving revision 1.44 diff -u -p -r1.44 sed.1 --- sed.1 22 Oct 2014 23:23:22 -0000 1.44 +++ sed.1 16 Jul 2015 19:15:14 -0000 @@ -47,6 +47,7 @@ .Op Fl aEnru .Op Fl e Ar command .Op Fl f Ar command_file +.Op Fl i Op Ar extension .Op Ar .Sh DESCRIPTION The @@ -94,6 +95,16 @@ Append the editing commands found in the .Ar command_file to the list of commands. The editing commands should each be listed on a separate line. +.It Fl i Ar extension +Edit files in-place, saving backups with the specified +.Ar extension . +If a zero-length +.Ar extension +is given, no backup will be saved. +It is not recommended to give a zero-length +.Ar extension +when in-place editing files, as you risk corruption or partial content +in situations where disk space is exhausted, etc. .It Fl r An alias for .Fl E , @@ -510,6 +521,12 @@ command, squeezing excess empty lines from standard input: .Bd -literal -offset indent $ sed -n ' +.Pp +The +.Fl i +option is a non-standard +.Fx +extension and may not be available on other operating systems. # Write non-empty lines. /./ { p @@ -543,7 +560,7 @@ utility is compliant with the specification. .Pp The flags -.Op Fl aEru +.Op Fl aEiru are extensions to that specification. .Pp The use of newlines to separate multiple commands on the command line