On Fri, Jul 17, 2015 at 12:57:12PM -0400, Brian Callahan wrote: > > > 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.
You mean gsed right? > 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 > > > -- Antoine