c - C Minishell添加管道

标签 c shell unix pipe pipeline

因此,我正在制作UNIX minishell,并尝试添加管道,因此我可以执行以下操作:

ps aux | grep dh | grep -v grep | cut -c1-5


但是,我无法将头缠绕在管道部分上。我替换所有的“ |”带有0的字符,然后将每行作为普通行运行。但是,我试图转移输出和输入。命令的输入必须是上一个命令的输出,命令的输出需要是下一个命令的输入。

我正在使用管道执行此操作,但是我不知道在哪里调用pipe()以及在哪里关闭它们。从主要处理函数processline()中,我有以下代码:

if((pix = findUnquotChar(line_itr, '|')))
{
    line_itr[pix++] = 0;
    if(pipe (fd) < 0) perror("pipe");
    processline(line_itr, inFD, fd[1], pl_flags);
    line_itr = &(line_itr[pix]);

    while((pix = findUnquotChar(line_itr, '|')) && pix < line_len)
    {
        line_itr[pix++] = 0;
        //? if(pipe (fd) < 0) perror("pipe");
        processline(line_itr, fd[0], fd[1] pl_flags);
        line_itr = &(line_itr[pix]);
    //? close(fd[0]);
    //? close(fd[1]);
    }
    return;
}


因此,我以递归方式(上面的代码在过程行中)在“ |”之间发送命令由流程线处理。您可以在上面的代码中看到我注释掉的位置,但不确定如何使其工作。 processline的第二个和第三个参数分别是inputFD和outputFD,因此我需要处理一个命令,将输出写入管道,然后在下一个命令上再次调用processline,但是这次的前一个命令的输出是输入。但这似乎似乎行不通,因为每次关闭fd [0]时,我都会丢失先前的输出。我是否需要两个独立的管道,可以用来来回翻转?

如果您需要任何其他信息,我只是很难看到单个管道是如何实现的。如果您想看一下,这里是整个流程线功能:

http://pastebin.com/YiEdaYdj

编辑:如果有人有实现管道的外壳的示例,我希望链接到源,到目前为止,我还无法在Google上找到一个。

编辑2:这是我的困境的一个示例:

echo a | echo b | echo c


所以首先我将这样称呼外壳:

processline("echo a", 0, fd[1], flags);

....

processline("echo b", fd[0], NOT_SURE_GOES_HERE[1], flags);

....

processline("echo c", NOT_SURE_GOES_HERE[0], NOT_SURE_EITHER[1], flags);


这些操作每个迭代发生一次,如您所见,我无法确定第二次和第三次(等等)迭代的输入文件描述符和输出文件描述符要传递什么。

最佳答案

这是一些适度通用但简单的代码来执行管道,这是我正在调用的pipeline程序。尽管我会将文件stderr.hstderr.c作为单独的文件存储在库中以与我的所有程序链接,但它在单个文件中是SSCCE。 (实际上,我的“真实” stderr.cstderr.h中有一组更复杂的功能,但这是一个很好的起点。)

该代码以两种方式运行。如果不提供任何参数,则它将运行内置管道:

who | awk '{print $1}' | sort | uniq -c | sort -n


这将计算每个人在系统上的登录次数,并按照会话数增加的顺序显示列表。或者,您可以使用要调用的命令行的参数序列来调用,使用带引号的管道'|'(或"|")分隔命令:

有效:

pipeline
pipeline ls '|' wc
pipeline who '|' awk '{print $1}' '|' sort '|' uniq -c '|' sort -n
pipeline ls


无效:

pipeline '|' wc -l
pipeline ls '|' '|' wc -l
pipeline ls '|' wc -l '|'


最后三个调用强制使用“管道作为分隔符”。该代码不会对每个系统调用进行错误检查。它确实对fork()execvp()pipe()进行错误检查,但跳过对dup2()close()的检查。它不包括针对所生成命令的诊断打印; -xpipeline选项将是一个明智的选择,使它可以打印出其功能的痕迹。它也不会以管道中最后一条命令的退出状态退出。

请注意,代码以分叉的孩子开始。子级将成为管道中的最后一个进程,但首先创建一个管道并派生另一个进程以运行管道中的较早进程。相互递归函数不太可能是解决问题的唯一方法,但是它们确实保留了最少的代码重复(早期代码草稿的exec_nth_command()内容在exec_pipeline()exec_pipe_command()中大量重复)。

这里的过程结构使得原始过程仅知道管道中的最后一个过程。可以通过以下方式重新设计事物:原始进程是管道中每个进程的父级,因此原始进程可以分别报告管道中每个命令的状态。我尚未修改代码以允许该结构;它会稍微复杂一些,尽管并不是那么可怕。

/* One way to create a pipeline of N processes */

/* stderr.h */
#ifndef STDERR_H_INCLUDED
#define STDERR_H_INCLUDED

static void err_setarg0(const char *argv0);
static void err_sysexit(char const *fmt, ...);
static void err_syswarn(char const *fmt, ...);

#endif /* STDERR_H_INCLUDED */

/* pipeline.c */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
/*#include "stderr.h"*/

typedef int Pipe[2];

/* 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(0, &status, 0)) != -1)
    {
        fprintf(stderr, "%d: child %d status 0x%.4X\n",
                (int)parent, (int)corpse, status);
    }
}

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

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

static void exec_arguments(int argc, char **argv)
{
    /* Split the command line into sequences of arguments */
    /* Break at pipe symbols as arguments on their own */
    char **cmdv[argc/2];            // Way too many
    char  *args[argc+1];
    int cmdn = 0;
    int argn = 0;

    cmdv[cmdn++] = &args[argn];
    for (int i = 1; i < argc; i++)
    {
        char *arg = argv[i];
        if (strcmp(arg, "|") == 0)
        {
            if (i == 1)
                err_sysexit("Syntax error: pipe before any command");
            if (args[argn-1] == 0)
                err_sysexit("Syntax error: two pipes with no command between");
            arg = 0;
        }
        args[argn++] = arg;
        if (arg == 0)
            cmdv[cmdn++] = &args[argn];
    }
    if (args[argn-1] == 0)
        err_sysexit("Syntax error: pipe with no command following");
    args[argn] = 0;
    exec_pipeline(cmdn, cmdv);
}

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    if (argc == 1)
    {
        /* Run the built in pipe-line */
        exec_pipeline(ncmds, cmds); 
    }
    else
    {
        /* Run command line specified by user */
        exec_arguments(argc, argv);
    }
    corpse_collector();
    return(0);
}

/* stderr.c */
/*#include "stderr.h"*/
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

static const char *arg0 = "<undefined>";

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

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);
}




信号与SIGCHLD

POSIX Signal Concepts部分讨论了SIGCHLD:

在SIG_DFL下:


如果默认动作是忽略信号,则信号的传送对过程没有影响。


在SIG_IGN下:


如果将SIGCHLD信号的操作设置为SIG_IGN,则在终止时,调用进程的子进程不得转换为僵尸进程。如果调用进程随后等待其子进程,并且该进程没有未等待的子进程转换为僵尸进程,则它将阻塞直到其所有子进程终止,并且wait()waitid()waitpid()应失败,并将errno设置为[ECHILD]


<signal.h>的描述有一个默认信号配置表,对于SIGCHLD,默认值为I(SIG_IGN)。



我在上面的代码中添加了另一个功能:

#include <signal.h>

typedef void (*SigHandler)(int signum);

static void sigchld_status(void)
{
    const char *handling = "Handler";
    SigHandler sigchld = signal(SIGCHLD, SIG_IGN);
    signal(SIGCHLD, sigchld);
    if (sigchld == SIG_IGN)
        handling = "Ignored";
    else if (sigchld == SIG_DFL)
        handling = "Default";
    printf("SIGCHLD set to %s\n", handling);
}


我在调用err_setarg0()之后立即调用了它,它在Mac OS X 10.7.5和Linux(RHEL 5,x86 / 64)上均报告“默认”。我通过运行来验证其操作:

(trap '' CHLD; pipeline)


在两个平台上,都报告“已忽略”,并且pipeline命令不再报告子项的退出状态。它没有得到它。

因此,如果程序忽略SIGCHLD,则它不会生成任何僵尸,但会等到其所有“子级”终止。也就是说,直到其所有直系子女终止为止;一个过程不能等待其子孙或更远的后代,不能等待其兄弟姐妹,也不能等待其祖先。

另一方面,如果SIGCHLD的设置为默认设置,则忽略信号并创建僵尸。

这是该程序编写时最方便的行为。 corpse_collector()函数具有一个循环,该循环从任何子级收集状态信息。一次只有一个孩子使用此代码。管道的其余部分作为管道中最后一个进程的子进程(子进程,子进程,...)运行。




但是我在僵尸/尸体方面遇到麻烦。我的老师让我用与您相同的方式来实现它,因为在“ cmd1”的情况下,cmd2不是cmd1 | cmd2 | cmd3的父项。除非我告诉我的外壳程序在每个进程(cmd1cmd2cmd3)上等待,而不是仅在最后一个进程(cmd3)上等待,否则整个管道将关闭,然后输出才能到达结束。我很难找到一种等待他们的好方法。我的老师说要使用WNOHANG。


我不确定我是否理解问题。使用我提供的代码,在3命令管道中,cmd3cmd2的父级,而cmd2cmd1的父级(而shell是cmd3的父级),因此该shell只能等待cmd3。我原先声明:


这里的过程结构使得原始过程仅知道管道中的最后一个过程。可以通过以下方式重新设计事物:原始进程是管道中每个进程的父级,因此原始进程可以分别报告管道中每个命令的状态。我尚未修改代码以允许该结构;它会稍微复杂一些,尽管并不是那么可怕。


如果您的Shell能够等待管道中的所有三个命令,则必须使用备用组织。

waitpid()描述包括:


pid参数指定一组请求状态的子进程。 waitpid()函数应仅从该集合返回子进程的状态:


如果pid等于(pid_t)-1,则请求任何子进程的状态。在这方面,waitpid()等效于wait()。
如果pid大于0,则它指定要为其请求状态的单个子进程的进程ID。
如果pid为0,则请求其进程组ID与调用进程的ID相等的任何子进程的状态。
如果pid小于(pid_t)-1,则请求其进程组ID等于pid的绝对值的任何子进程的状态。


options参数是根据标头中定义的零个或多个以下标志的按位或运算而构造的:

...

万航
如果pid指定的子进程之一的状态不是立即可用的,则waitpid()函数不应暂停调用线程的执行。

...


这意味着,如果您使用的是进程组,并且外壳程序知道管道在哪个进程组中运行(例如,由于管道是由第一个进程放入其自己的进程组中的),那么父级可以等待适当的时间孩子终止。

...漫步...我认为这里有一些有用的信息;我可能正在写更多东西,但我的脑子一片空白。

关于c - C Minishell添加管道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13636252/

相关文章:

shell - 如何在 UNIX 中将两个行号之间的文本打印到新文件中

不带参数和括号的调用函数

运行时的 C 结构自省(introspection)

c - 到达时间的 SJF 算法无法正常工作

xml - 只比较 linux 中的 xml 标签

linux - 如何去掉文档的一些新行

xml - 如何在shell中注释掉xml文件中的字符串

c - Eclipse stdio.h 错误

linux - 如何在 Linux 上使用 bash 增加文本文件中的浮点值?

linux - Motif Widget Toolkit 的 Perl 绑定(bind)