如何从stdin写入C中的管道

时间:2013-11-07 01:20:23

标签: c stdin pipe

我有一个程序被参数调用:

 pipeline -f outfile2 < infile > outfile1

它应该模仿bash脚本的功能:

 (rev | sort | uniq -c | tee outfile2| wc) < infile > outfile1

我理解如何设置管道,但我不明白如何获得初始读数。我已经将outfile2文件名捕获到一个变量,但我认为我可以保留外部两个,操作系统将分别将它们作为stdin和stdout。我如何在父进程中以编程方式将stdin读入管道?

更新如下:以下代码不使用命令行参数修改输出文件:pipeline -f outfile2 outfile1
显然有真正的文件名。

主:

char *fp;
int c;

/* parse command line arguments */
parse_argv(argc, argv);

if (pipe(pipeOne) == -1){ /* Create the pipe */
    perror("pipe one");
    error(-1);
}


if (pipeOne[1] != 1){
    dup2(pipeOne[1], stdout);
    exit(-1);
}

while ((c = getc(stdin)) != EOF){
      if ((write(pipeOne[1], c, 1)) < 1)
      {
          perror("Write to pipe failed.");
          exit(-1);
     }
 }

wc_child();
/* begin reading file to pipe */
if (close(pipeOne[0]) == -1){ /* Close unused read end */
    perror("closing pipes");
    exit(-1);
}
 close (pipeOne[1]);

wc_child:

void wc_child(){
int numread;
switch (fork()) { /* Create a child process */
    case -1:
        perror("fork for rev_child");
    case 0: /* Child */
        /*if (close(pipeOne[1]) == -1) /* Close unused write end */
        /*perror("closing pipes");*/
        if (close(pipeOne[1]) == -1){ /* Close unused write end */
            perror("closing pipes");
            exit(-1);
            }
        dup2(pipeOne[0], stdin);
        /* dup2(pipeFive[0], stdin);*/
        /* Not sure how to set up stdout since it will be going to a file.*/

        for(;;){
            execlp("wc","wc");
        }

        break;
    default: /* Parent */
        return 0;
}
return -1; /*return -1 because it never should reach this code.  If it does that indicates a problem*/
}

2 个答案:

答案 0 :(得分:2)

您可以#include <unistd.h>然后直接从文件描述符STDIN_FILENO中读取。 dup2(pipeOne[0], stdin);也可能使用STDIN_FILENO,因为stdinFILE *而不是描述符。

但是,我认为你真的不想做任何这样的事情。您应该将stdin挂钩到管道的写入端(以及管道的下一个阶段到读取端),然后执行以开始您的第一阶段,而不是从stdout读取管道。然后,调用的子进程将从stdin读取,转换输入并写入stdout,用数据填充管道。

答案 1 :(得分:1)

你可以通过很多不同的方式来做到这一点;使用fgets()读取数据,然后write()仔细写入管道可能就足够了:

char line[4096];
while (fgets(line, sizeof(line), stdin) != 0)
{
    size_t len = strlen(line);
    if (write(pipefd[1], line, len) != len)
    {
        fprintf(stderr, "Failed to write to pipe\n");
        exit(1);
    }
}

要模拟管道,您实际上不需要将任何东西从标准输入复制到管道;你可以简单地让rev读取标准输入。

以下代码很快从我对C Minishell — Adding Pipelines的答案中得出,可以满足您的需求。

/*
** How to write from stdin to a pipe in C
** https://stackoverflow.com/questions/19826211
**
** Write program pipeline to be invoked as:
**
**     pipeline -f outfile2 < infile > outfile1
**
** It should mimic the functionality of the bash script:
**
**     (rev | sort | uniq -c | tee outfile2 | wc) < infile > outfile1
**
** Refactored, with equivalent functionality:
**
**     rev < infile | sort | uniq -c | tee outfile2 | wc > outfile1
**
** Based on answer to SO 13636252 C Minishell adding pipelines
*/

/* pipeline.c */
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

/*  who | awk '{print $1}' | sort | uniq -c | sort -n */
static char *cmd0[] = { "rev",        0 };
static char *cmd1[] = { "sort",       0 };
static char *cmd2[] = { "uniq", "-c", 0 };
static char *cmd3[] = { "tee", 0,     0 };
static char *cmd4[] = { "wc",         0 };

static char **cmds[] = { cmd0, cmd1, cmd2, cmd3, cmd4 };
static int   ncmds = sizeof(cmds) / sizeof(cmds[0]);

static char const usestr[] = "[-f filename]";

typedef int Pipe[2];

/* These functions normally declared in stderr.h */
static void err_setarg0(const char *argv0);
static void err_sysexit(char const *fmt, ...);
static void err_syswarn(char const *fmt, ...);
static void err_usage(char const *usestr);

/* exec_nth_command() and exec_pipe_command() are mutually recursive */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output);

/* With the standard output plumbing sorted, execute Nth command */
static void exec_nth_command(int ncmds, char ***cmds)
{
    assert(ncmds >= 1);
    if (ncmds > 1)
    {
        pid_t pid;
        Pipe input;
        if (pipe(input) != 0)
            err_sysexit("Failed to create pipe");
        if ((pid = fork()) < 0)
            err_sysexit("Failed to fork");
        if (pid == 0)
        {
            /* Child */
            exec_pipe_command(ncmds-1, cmds, input);
        }
        /* Fix standard input to read end of pipe */
        dup2(input[0], 0);
        close(input[0]);
        close(input[1]);
    }
    execvp(cmds[ncmds-1][0], cmds[ncmds-1]);
    err_sysexit("Failed to exec %s", cmds[ncmds-1][0]);
    /*NOTREACHED*/
}

/* Given pipe, plumb it to standard output, then execute Nth command */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output)
{
    assert(ncmds >= 1);
    /* Fix stdout to write end of pipe */
    dup2(output[1], 1);
    close(output[0]);
    close(output[1]);
    exec_nth_command(ncmds, cmds);
}

/* Execute the N commands in the pipeline */
static void exec_pipeline(int ncmds, char ***cmds)
{
    assert(ncmds >= 1);
    pid_t pid;
    if ((pid = fork()) < 0)
        err_syswarn("Failed to fork");
    if (pid != 0)
        return;
    exec_nth_command(ncmds, cmds);
}

/* Collect dead children until there are none left */
static void corpse_collector(void)
{
    pid_t parent = getpid();
    pid_t corpse;
    int   status;
    while ((corpse = waitpid(-1, &status, 0)) != -1)
    {
        fprintf(stderr, "%d: child %d status 0x%.4X\n",
                (int)parent, (int)corpse, status);
    }
}

int main(int argc, char **argv)
{
    int opt;
    char *filename = "outfile2";    // Default file name

    err_setarg0(argv[0]);

    while ((opt = getopt(argc, argv, "f:")) != -1)
    {
        switch (opt)
        {
        case 'f':
            filename = optarg;
            break;
        default:
            err_usage(usestr);
            break;
        }
    }
    if (optind != argc)
        err_usage(usestr);

    /* Set the file name for tee to write to */
    cmd3[1] = filename;

    exec_pipeline(ncmds, cmds);
    corpse_collector();
    return(0);
}

/* Normally in stderr.c */
static const char *arg0 = "<undefined>";

static void err_setarg0(const char *argv0)
{
    arg0 = argv0;
}

static void err_usage(char const *usestr)
{
    fprintf(stderr, "Usage: %s %s\n", arg0, usestr);
    exit(1);
}

static void err_vsyswarn(char const *fmt, va_list args)
{
    int errnum = errno;
    fprintf(stderr, "%s:%d: ", arg0, (int)getpid());
    vfprintf(stderr, fmt, args);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    putc('\n', stderr);
}

static void err_syswarn(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    err_vsyswarn(fmt, args);
    va_end(args);
}

static void err_sysexit(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    err_vsyswarn(fmt, args);
    va_end(args);
    exit(1);
}

示例输出(outfile1infile是程序源代码的副本时):

 125     691    4879

示例输出(outfile2的第一行和最后10行):

  22 
   1 )... ,tmf* tsnoc rahc(nrawsys_rre diov citats
   1 )... ,tmf* tsnoc rahc(tixesys_rre diov citats
   1 )0 < ))(krof = dip(( fi    
   1 )0 < ))(krof = dip(( fi        
   1 )0 =! )tupni(epip( fi        
   1 )0 =! dip( fi    
   1 )0 =! munrre( fi    
   1 )0 == dip( fi        
   1 )0vgra* rahc tsnoc(0grates_rre diov citats
...
   1 >h.tressa< edulcni#
   1 C ni epip a ot nidts morf etirw ot woH **
   1 eman elif tluafeD //    ;"2eliftuo" = emanelif* rahc    
   1 senilepip gnidda llehsiniM C 25263631 OS ot rewsna no desaB **
  10 {
   3 {    
   2 {        
  10 }
   3 }    
   2 }  

(我注意到,无论行是否反转,字数都是相同的,因此rev命令在管道中没有用处。)有一些诊断输出被捕获;你很容易压制它。