OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
 
[patch] sed(1) - edit files in-place

From: Leonardo Chiquitto Filho (leonardoiken.com.br)
Date: Sun Oct 02 2005 - 22:53:20 CDT


Hello,

  Here's a patch (ported from FreeBSD) that adds in-place editing support
  to sed(1).
  It'd be nice to get some testing on this.

Thanks,
Leonardo

Index: extern.h
===================================================================
RCS file: /cvs/src/usr.bin/sed/extern.h,v
retrieving revision 1.4
diff -u -r1.4 extern.h
--- extern.h 3 Jun 2003 02:56:16 -0000 1.4
+++ extern.h 3 Oct 2005 04:09:16 -0000
-42,7 +42,8
 extern int appendnum;
 extern int lastline;
 extern int 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);
Index: main.c
===================================================================
RCS file: /cvs/src/usr.bin/sed/main.c,v
retrieving revision 1.9
diff -u -r1.9 main.c
--- main.c 10 Jun 2003 22:20:50 -0000 1.9
+++ main.c 3 Oct 2005 04:09:16 -0000
-45,6 +45,8
 #endif /* not lint */
 
 #include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
 
 #include <ctype.h>
 #include <errno.h>
-55,6 +57,8
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <libgen.h>
+#include <err.h>
 
 #include "defs.h"
 #include "extern.h"
-88,18 +92,27
  */
 static struct s_flist *files, **fl_nextp = &files;
 
+FILE *infile; /* Current input file */
+FILE *outfile; /* Current output file */
+
 int aflag, eflag, nflag;
+static int rval; /* Exit status */
 
 /*
  * Current file and line number; line numbers restart across compilation
  * units, but span across input files.
  */
-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) */
 u_long linenum;
+const char *inplace; /* Inplace edit file extension */
 int lastline; /* TRUE on the last line of the last file */
 
 static void add_compunit(enum e_cut, char *);
 static void add_file(char *);
+__dead void usage(void);
 
 int
 main(int argc, char *argv[])
-107,7 +120,9
         int c, fflag;
 
         fflag = 0;
- while ((c = getopt(argc, argv, "ae:f:n")) != -1)
+ inplace = NULL;
+
+ while ((c = getopt(argc, argv, "ae:f:i:n")) != -1)
                 switch (c) {
                 case 'a':
                         aflag = 1;
-120,14 +135,15
                         fflag = 1;
                         add_compunit(CU_FILE, optarg);
                         break;
+ case 'i':
+ inplace = optarg;
+ break;
                 case 'n':
                         nflag = 1;
                         break;
                 default:
                 case '?':
- (void)fprintf(stderr,
-"usage:\tsed script [-an] [file ...]\n\tsed [-an] [-e script] ... [-f script_file] ... [file ...]\n");
- exit(1);
+ usage();
                 }
         argc -= optind;
         argv += optind;
-153,6 +169,15
         exit (0);
 }
 
+__dead void
+usage(void)
+{
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: sed script [-an] [-i extension] [file ...]",
+ " sed [-an] [-i extension] [-e script] ... [-f script_file] ... [file ...]");
+ exit (EXIT_FAILURE);
+}
+
 /*
  * Like fgets, but go through the chain of compilation units chaining them
  * together. Empty strings and files are ignored.
-245,69 +270,114
 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;
+ 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)
+ errx(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) {
+ warn("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(FATAL, "%s", fname);
+ if (!(sb.st_mode & S_IFREG))
+ errx(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))
+ errx(FATAL, "%s: name too long",
+ fname);
+ }
+ len = snprintf(tmpfname, sizeof(tmpfname),
+ "%s/.tmpXXXXXX%s", dirname(fname), basename(fname));
+ if (len >= sizeof(tmpfname))
+ errx(FATAL, "%s: name too long", fname);
+ unlink(tmpfname);
+ if ((outfile = fdopen(mkstemp(tmpfname), "w")) == NULL)
+ err(FATAL, "%s", fname);
+ outfname = tmpfname;
+ } else {
+ outfile = stdout;
+ outfname = "stdout";
+ }
+ if ((infile = fopen(fname, "r")) == NULL) {
+ warn("%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))
- err(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO));
+ p = fgetln(infile, &len);
+ if (ferror(infile))
+ errx(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO));
+ if (len != 0 && p[len - 1] == '\n')
+ len--;
         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);
 }
 
Index: process.c
===================================================================
RCS file: /cvs/src/usr.bin/sed/process.c,v
retrieving revision 1.12
diff -u -r1.12 process.c
--- process.c 7 Nov 2003 02:58:23 -0000 1.12
+++ process.c 3 Oct 2005 04:09:16 -0000
-81,7 +81,7
 size_t maxnsub;
 regmatch_t *match;
 
-#define OUT(s) { fwrite(s, sizeof(u_char), psl, stdout); }
+#define OUT(s) { fwrite(s, sizeof(u_char), psl, outfile); fputc('\n', outfile); }
 
 void
 process(void)
-124,7 +124,7
                                 pd = 1;
                                 psl = 0;
                                 if (cp->a2 == NULL || lastaddr)
- (void)printf("%s", cp->t);
+ (void)fprintf(outfile, "%s", cp->t);
                                 break;
                         case 'd':
                                 pd = 1;
-156,7 +156,7
                                 cspace(&HS, ps, psl, 0);
                                 break;
                         case 'i':
- (void)printf("%s", cp->t);
+ (void)fprintf(outfile, "%s", cp->t);
                                 break;
                         case 'l':
                                 lputs(ps);
-248,7 +248,7
                         case '}':
                                 break;
                         case '=':
- (void)printf("%lu\n", linenum);
+ (void)fprintf(outfile, "%lu\n", linenum);
                         }
                         cp = cp->next;
                 } /* for all cp */
-446,12 +446,12
                         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;
 }
 
-463,7 +463,9
         struct winsize win;
         static int termwidth = -1;
 
- if (termwidth == -1)
+ if (outfile != stdout)
+ termwidth = 60;
+ if (termwidth == -1) {
                 if ((p = getenv("COLUMNS")))
                         termwidth = atoi(p);
                 else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
-471,31 +473,32
                         termwidth = win.ws_col;
                 else
                         termwidth = 60;
+ }
 
         for (count = 0; *s; ++s) {
                 if (count >= termwidth) {
- (void)printf("\\\n");
+ (void)fprintf(outfile, "\\\n");
                         count = 0;
                 }
                 if (isascii(*s) && isprint(*s) && *s != '\\') {
- (void)putchar(*s);
+ (void)fputc(*s, outfile);
                         count++;
                 } else {
                         escapes = "\\\a\b\f\n\r\t\v";
- (void)putchar('\\');
+ (void)fputc('\\', outfile);
                         if ((p = strchr(escapes, *s))) {
- (void)putchar("\\abfnrtv"[p - escapes]);
+ (void)fputc("\\abfnrtv"[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
Index: sed.1
===================================================================
RCS file: /cvs/src/usr.bin/sed/sed.1,v
retrieving revision 1.23
diff -u -r1.23 sed.1
--- sed.1 4 Oct 2004 21:37:49 -0000 1.23
+++ sed.1 3 Oct 2005 04:09:17 -0000
-47,6 +47,7
 .Op Fl an
 .Op Fl e Ar command
 .Op Fl f Ar command_file
+.Op Fl i Ar extension
 .Op Ar file ...
 .Sh DESCRIPTION
 The
-89,6 +90,16
 .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 n
 By default, each line of input is echoed to the standard output after
 all of the commands have been applied to it.