On 07/17/15 12:10, Jasper Lievisse Adriaanse wrote: > 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? >
Cool. This saves me having to install coreutils just to work on some school projects. ok bcallah@, fwiw. > 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 >