Presenting "splice", a useful UNIX command (probably requires a 4.4BSD-based system):
/*
* splice is a program intended to be placed in a pipeline. It reads all
* input from stdin, opens your favorite text editor ($VISUAL, $EDITOR,
* or vi) on that input, then prints your manually changed version of
* the text on stdout.
*
* This is done by forking and reopening stdin and stdout as /dev/tty
* before invoking the text editor.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <paths.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#define MAX_EDITOR_ARGS 30
char *editor_args[MAX_EDITOR_ARGS+2]; /* 1 for filename arg, 1 for NULL */
int nargs = 0;
char tmppath[] = "/tmp/tmp.XXXXXXXXXX";
int fd = -1;
char *strndup(const char *src, size_t len)
{
char *s;
s = malloc(len+1);
if (s == NULL) err(1, NULL);
memcpy(s, src, len);
s[len] = '\0';
return s;
}
void string_to_args(const char *str)
{
const char *p = str, *space;
for (;;)
{
if (nargs == MAX_EDITOR_ARGS)
errx(1, "too many editor arguments");
while(*p == ' ')
p++;
if (*p == '\0')
break;
space = strchr(p, ' ');
if (space == NULL)
space = &p[strlen(p)];
editor_args[nargs++] = strndup(p, space - p);
p = space;
}
editor_args[nargs] = NULL;
}
void save_stdin(void)
{
char buf[BUFSIZ];
int bytesread;
fd = mkstemp(tmppath);
if (fd == -1)
err(1, "cannot open temporary file");
while ((bytesread = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
write(fd, buf, bytesread);
close(fd);
}
void replicate_tmpfile(void)
{
char buf[BUFSIZ];
int bytesread;
fd = open(tmppath, O_RDONLY|O_NOFOLLOW, 0);
if (fd == -1)
err(1, "cannot reopen temporary file");
while ((bytesread = read(fd, buf, BUFSIZ)) > 0)
write(STDOUT_FILENO, buf, bytesread);
close(fd);
}
void unlink_tmpfile(void)
{
if (unlink(tmppath) == -1)
warn("cannot unlink temporary file");
}
void setup_editor_args(void)
{
const char *edname;
edname = getenv("VISUAL");
if (edname == NULL) edname = getenv("EDITOR");
if (edname == NULL) edname = "vi";
/*
* Split up the editor command into arguments,
* e.g. "mg" and "-n".
*/
string_to_args(edname);
}
int start_editor(void)
{
pid_t pid;
int status;
/* To the editor arguments, add the temporary filename to edit. */
editor_args[nargs++] = tmppath;
editor_args[nargs] = NULL;
switch (vfork())
{
case -1:
unlink_tmpfile();
err(1, "vfork");
case 0:
/* Reopen the terminal as stdin and stdout. */
fd = open(_PATH_TTY, O_RDWR);
if (fd == -1)
{
warn("cannot open " _PATH_TTY);
_exit(1);
}
if (dup2(fd, STDIN_FILENO) == -1)
{
warn("can't dup2 to stdin");
_exit(1);
}
if (dup2(fd, STDOUT_FILENO) == -1)
{
warn("can't dup2 to stdout");
_exit(1);
}
close(fd);
execvp(editor_args[0], editor_args);
/* execvp only returns if there is an error. */
warn("%s", editor_args[0]);
_exit(1);
}
pid = wait(&status);
if (pid == -1)
err(1, "wait");
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}
int main(void)
{
int ret;
setup_editor_args();
save_stdin();
ret = start_editor();
if (ret != 0)
warnx("non-zero return value from editor");
replicate_tmpfile();
unlink_tmpfile();
return ret;
}