Maybe this will give you some inspiration.
Here's how Bash handles command pipes:
(I stripped out all the error handling crap, rewrote a couple parts, and added some comments to make it a little clearer)
Code:
static int execute_pipeline (COMMAND *command,
int asynchronous,
int pipe_in,
int pipe_out,
struct fd_bitmap *fds_to_close)
{
int prev, fildes[2], ignore_return, exec_result;
COMMAND *cmd;
ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
prev = pipe_in;
cmd = command;
// Loops until it reaches a 'normal' command
// (a command that isn't a connection)
while (cmd && cmd->type == cm_connection &&
cmd->value.Connection && cmd->value.Connection->connector == '|')
{
/* Make a pipeline between the two commands. */
pipe(fildes);
if (ignore_return && cmd->value.Connection->first)
cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN;
execute_command_internal (cmd->value.Connection->first, // the command to execute
asynchronous, // boolean flag
prev, // the input pipe
fildes[1], // the output pipe
fd_bitmap); // error handling crap
if (prev >= 0)
close (prev);
prev = fildes[0];
close(fildes[1]);
cmd = cmd->value.Connection->second;
}
// end while loop
/* Now execute the rightmost command in the pipeline. */
if (ignore_return && cmd)
cmd->flags |= CMD_IGNORE_RETURN;
exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close);
if (prev >= 0)
close (prev);
return (exec_result);
}