Writing to stdin for homebrew-piping
I'm writing a small program to mimic the Unix piping system (e.g. "cat file1.txt | grep keyword | wc
").
I can manage to collect the output of the programs from stdout using dup2()
and pipes but I can't work out how to feed it on to the next process.
At first I thought it was simple, just something like:
write(stdin, buffer, buffer_size);
but that's not working for me. There is a lot of information out there about stdout but not nearly as much about stdin.
Any help wou开发者_运维知识库ld be amazing.
As I understand your question, you mean that you want to execute the command cat file1.txt | grep keyword | wc
from within your program.
You can do this by setting up some pipes for in between the programs with pipe()
.
Then fork
a few times for each program you want to execute.
In the different forked processes set the stdin
and stdout
to the correct endings of the created pipes.
When everything is setup, you can call exec()
to execute the different programs like cat
, grep
and wc
with the correct parameters.
Your program can than just read from the pipe after the wc
program to get the final output.
Your code won't be doing any reading or writing; it will just be plumbing (sorting out the pipes).
You will create two pipes and two children. The first child will run the cat
command; the second the grep
command, and the parent will run the wc
program. Since there are no pathnames for the programs, the code will use execvp()
.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
static void err_exit(const char *fmt, ...);
static void exec_head(char **args, int *p1, int *p2);
static void exec_tail(char **args, int *p1, int *p2);
static void exec_middle(char **args, int *p1, int *p2);
static void exec_cmd(char **args, int *p1, int *p2);
int main(void)
{
char *cmd1[] = { "cat", "file1.txt", 0 };
char *cmd2[] = { "grep", "keyword", 0 };
char *cmd3[] = { "wc", 0 };
int pipe12[2];
int pipe23[2];
pid_t pid1;
pid_t pid2;
if (pipe(pipe12) != 0 || pipe(pipe23) != 0)
err_exit("Failed to create pipes");
if ((pid1 = fork()) < 0)
err_exit("Failed to fork (child1)");
else if (pid1 == 0)
exec_head(cmd1, pipe12, pipe23);
else if ((pid2 = fork()) < 0)
err_exit("Failed to fork (child2):");
else if (pid2 == 0)
exec_middle(cmd2, pipe12, pipe23);
else
exec_tail(cmd3, pipe12, pipe23);
/*NOTREACHED*/
return(-1);
}
/* Execute head process in pipeline */
/* Close both pipes in p2; connect write end of pipe p1 to stdout */
static void exec_head(char **args, int *p1, int *p2)
{
if (dup2(p1[1], 1) < 0)
err_exit("Failed to duplicate file descriptor to stdout (%s)", args[0]);
exec_cmd(args, p1, p2);
/*NOTREACHED*/
}
/* Execute tail process in pipeline */
/* Close both pipes in p1; connect read end of p2 to standard input */
static void exec_tail(char **args, int *p1, int *p2)
{
if (dup2(p2[0], 0) < 0)
err_exit("Failed to duplicate file descriptor to stdin (%s)", args[0]);
exec_cmd(args, p1, p2);
/*NOTREACHED*/
}
/* Execute middle command in pipeline */
/* Connect read end of p1 to stdin; connect write end of p2 to stdout */
static void exec_middle(char **args, int *p1, int *p2)
{
if (dup2(p1[0], 0) < 0)
err_exit("Failed to duplicate file descriptor to stdin (%s)", args[0]);
if (dup2(p2[1], 1) < 0)
err_exit("Failed to duplicate file descriptor to stdout (%s)", args[0]);
exec_cmd(args, p1, p2);
/*NOTREACHED*/
}
/* Close all descriptors for pipes p1, p2 and exec command */
static void exec_cmd(char **args, int *p1, int *p2)
{
close(p1[0]);
close(p1[1]);
close(p2[0]);
close(p2[1]);
execvp(args[0], args);
err_exit("Failed to execute %s", args[0]);
/*NOTREACHED*/
}
static void err_exit(const char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, ":%d: %s", errnum, strerror(errnum));
putc('\n', stderr);
exit(EXIT_FAILURE);
}
With a bit more work on refactoring, and handling the pipes carefully, you could eliminate the exec_head()
, exec_tail()
and exec_middle()
functions (specify descriptor to become stdin, or -1 if it should leave stdin alone; specify descriptor to become stdout, or -1 if it should leave stdout alone), and you can streamline the pipe handling by using a single array of 4 and modifying the calls to the pipe()
system call accordingly. That then becomes generalizable without much difficulty... The code should probably be using STDIN_FILENO
and STDOUT_FILENO
instead of 0 and 1 for the second argument to dup2()
. There aren't defined constants for the read and write ends of a pipe (enum { PIPE_READ, PIPE_WRITE };
might be useful here).
精彩评论