【问题标题】:Trying to build a shell with pipelines and background尝试构建一个带有管道和背景的外壳
【发布时间】:2020-07-13 16:18:05
【问题描述】:

您好,我正在尝试重新创建 shell,但遇到了两个主要问题:

1.执行一个命令后,它就完成了程序

2。管道不起作用

这是处理管道、重定向的代码部分......

int pfd[2];
if (pipe(pfd) < 0) exit(-1);
for (int i = 0; i < cmd.pipes; i++) {
    pid_t pid;
    pid = fork();
    int fd;
    if (pid < 0) exit(-1);
    else if (pid == 0) {
        close(pfd[0]);
        dup2(pfd[1], STDOUT_FILENO);
        close(pfd[1]);
        if (cmd.filev[0] != NULL && i == 0) {
            fd = open(cmd.filev[0], O_RDONLY, 0);
            dup2(fd, STDIN_FILENO);
            close(fd);
        }
        if (cmd.filev[2] != NULL) {
            fd = creat(cmd.filev[2], 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        if (execvp(cmd.argv[i][0], cmd.argv[i]) < 0)
            levenshtein(cmd.argv[i][0], commands);
    } else if (pid > 0) {
        if (cmd.bg > 0) wait(NULL);
        close(pfd[1]);
        dup2(pfd[0], STDIN_FILENO);
        close(pfd[0]);
        if (cmd.filev[1] != NULL && i == (cmd.pipes - 1)) {
            fd = creat(cmd.filev[1], 0644);
            dup2(fd, STDOUT_FILENO);
            close(fd);
        }
        if (cmd.filev[2] != NULL) {
            fd = creat(cmd.filev[2], 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        if (execvp(cmd.argv[i][0], cmd.argv[i]) < 0)
            levenshtein(cmd.argv[i][0], commands);
    }
}

警察局: Levenshtein 是一个在用户拼写错误时进行处理的函数

【问题讨论】:

  • 查看structcmd 声明和其他一些相关代码可能会有所帮助。另外,例如,请参阅我的回答:*.com/questions/52823093/fd-leak-custom-shell/…
  • cmd 实际上是一个结构,我在其中保存已解析的输入(argv(命令)、管道(命令数)、bg(背景信号)和 filev[3](重定向))
  • 如果管道中有 N 条命令,则需要 fork N 次。如果父进程用于运行其中一个进程,那么它将提前终止。

标签: c shell fork pid dup2


【解决方案1】:

这需要一点重构...

如果有 N 个流水线阶段,我们需要 N-1 个单独的 pipe 调用。代码只有一个。

每个流水线阶段都可以有自己的/私有的stderr 转移(例如):

cmd0 | cmd1 2>cmd1_stderr | cmd2 2>cmd2_stderr | cmd3

但是,代码假设 all stderr 将是相同的。

而且,它假设它必须打开stderr [at all]。如果我们有类似上面的内容,孩子应该只打开stderr。否则,每个孩子都应该继承父母的stderr [并且做什么]。

parent 进程正在做只有 child 应该做的事情(例如):更改 stdin/stdout/stderr 并执行命令。它应该主要是为了方便管道。

父级在创建/分叉循环中执行wait [针对每个子级]。这会导致父级在每一步都阻塞。

父母应该只在 second 循环中执行wait 并立即等待所有孩子。

因为您对cmdstruct 定义没有发布,所以我不得不猜测一下意图。但是,我认为你应该有一个结构的 array,每个命令一个,而不是将 all 命令的 all 参数放在一个结构。

我创建了两个版本的代码。一个带有错误注释。第二个被清理和重组。两者都未经测试,但应该会给你一些想法。


这是带注释的版本:

// NOTE/BUG: each command in the pipeline can have its own/private
// stderr diversion
#if 0
struct cmd {
    int pipes;
    char *filev[3];
    char **argv[MAXCMD][MAXARG];
};
#else
struct cmd {
    char *stderr;
    char *argv[100];
};
#endif

// NOTE/BUG: we need separate filev [and argv] for each pipeline stage
// so we need an _array_ of structs
int cmdcnt;
struct cmd cmdlist[30];

void
pipeline(void)
{
    int pfd[2];

// NOTE/BUG: this only creates a single pipe -- we need N-1 pipes
#if 0
    if (pipe(pfd) < 0)
        exit(-1);
#endif

// NOTE/BUG: if child does _not_ have a private stderr it should just
// use the parent's stderr _unchanged_

    for (int i = 0; i < cmdcnt; i++) {
        pid_t pid;

// NOTE/BUG: here is the correct place to create the pipe
        pid = fork();
        int fd;

        if (pid < 0)
            exit(-1);

        char **argv = cmd->argv;
        char **filev = cmd->filev;

        // child process
        if (pid == 0) {
            close(pfd[0]);
            dup2(pfd[1], STDOUT_FILENO);
            close(pfd[1]);

// NOTE/BUG: this does _not_ connect the input of cmd[N] to cmd[N-1]
#if 0
            if (filev[0] != NULL && i == 0) {
                fd = open(filev[0], O_RDONLY, 0);
                dup2(fd, STDIN_FILENO);
                close(fd);
            }
#endif

            if (filev[2] != NULL) {
                fd = creat(filev[2], 0644);
                dup2(fd, STDERR_FILENO);
                close(fd);
            }

            if (execvp(cmd->argv[i][0], cmd->argv[i]) < 0)
                levenshtein(cmd->argv[i][0], commands);
        }

        // parent process
        if (pid > 0) {
// NOTE/BUG: parent should _not_ wait in the middle of the creation
// loop
            if (cmd->bg > 0)
                wait(NULL);

// NOTE/BUG: _parent_ should _not_ change its stdin/stderr/stdout

            close(pfd[1]);
            dup2(pfd[0], STDIN_FILENO);
            close(pfd[0]);

            if (filev[1] != NULL && i == (cmd->pipes - 1)) {
                fd = creat(filev[1], 0644);
                dup2(fd, STDOUT_FILENO);
                close(fd);
            }

            if (filev[2] != NULL) {
                fd = creat(filev[2], 0644);
                dup2(fd, STDERR_FILENO);
                close(fd);
            }

// NOTE/BUG: _parent_ should _not_ execute the command
            if (execvp(cmd->argv[i][0], cmd->argv[i]) < 0)
                levenshtein(cmd->argv[i][0], commands);
        }
    }
}

这是重构后的版本:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

struct cmd {
    char *stderr;
    char *argv[100];
};

char *filev[3];

#define CLOSEME(_fd) \
    do { \
        if (_fd < 0) \
            break; \
        close(_fd); \
        _fd = -1; \
    } while (0)

int run_in_background;
int cmdcnt;
struct cmd cmdlist[30];

int
pipeline(void)
{
    int pfd[2] = { -1, -1 };
    struct cmd *cmd;
    char *file;
    int oldfd;
    pid_t pid;
    pid_t lastpid = -1;

    // open the common stderr
    int errfd = -1;
    if (filev[2] != NULL)
        errfd = open(filev[2],O_APPEND | O_CREAT);

    // start all commands
    for (int i = 0; i < cmdcnt; i++) {
        int iam_first = (i == 0);
        int iam_last = (i == (cmdcnt - 1));

        cmd = &cmdlist[i];

        // get previous stage pipe descriptor
        oldfd = pfd[0];

        // create the pipe to the next stage
        if (! iam_last) {
            if (pipe(pfd) < 0)
                exit(1);
        }

        pid = fork();
        lastpid = pid;

        int fd;

        if (pid < 0)
            exit(-1);

        char **argv = cmd->argv;

        // parent process
        if (pid > 0) {
            CLOSEME(pfd[1]);
            continue;
        }

        // child process ...

        // open stdin for _first_ command
        fd = -1;
        if (iam_first) {
            file = filev[0];
            if (file != NULL) {
                fd = open(file, O_RDONLY, 0);
            }
        }

        // connect stdin to previous stage pipe
        else {
            fd = oldfd;
            oldfd = -1;
        }

        // connect stdin to correct source
        if (fd >= 0) {
            dup2(fd, STDIN_FILENO);
            close(fd);
        }
        CLOSEME(oldfd);

        // connect to stderr
        file = cmd->stderr;
        if (file != NULL) {
            fd = creat(file, 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        else {
            if (errfd >= 0)
                dup2(errfd, STDERR_FILENO);
        }
        CLOSEME(errfd);

        // connect stdout
        // NOTE: does _not_ handle ">> outf" [only does "> outf"]
        fd = -1;
        if (iam_last) {
            file = filev[1];
            if (file != NULL) {
                fd = open(file, O_WRONLY | O_CREAT, 0644);
                dup2(fd, STDOUT_FILENO);
                close(fd);
            }
        }

        // execute the command
        execvp(argv[0], argv);
        exit(9);
    }

    CLOSEME(errfd);

    int status;
    int last_status = 0;

    // parent waits for all pipeline stages to complete
    if (! run_in_background) {
        while (1) {
            pid = wait(&status);
            if (pid <= 0)
                break;
            if (pid == lastpid) {
                last_status = status;
                break;
            }
        }
    }

    return last_status;
}

【讨论】: