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

Reply via email to