使用 C 在 Linux shell 中实现 input/output 重定向

Implementing input/output redirection in a Linux shell using C

我正在尝试使用 C 为 Linux 创建一个基本的 shell。在我尝试进行输出重定向之前我一直在使用它,但它只会破坏所有内容。当我 运行 这段代码时,它直接进入 fork() 的默认情况。我不知道为什么。如果我摆脱了子进程中的 for 循环,它就可以工作,但即使有了 for 循环,我也不明白为什么从未进入子进程。如果我将打印语句放在子进程的顶部,它不会被打印出来。

当我在命令行中 运行 时,我得到提示并输入类似 "ls" 的内容,这在我添加 for 循环之前有效,但现在我只得到“% am i here " 如果我​​按回车键,它只会一直给我相同的行。我的目标是能够键入 "ls > output" 并使其正常工作。我认为输入重定向有效,但老实说我什至没有玩过它,因为我对输出重定向发生了什么感到非常困惑。任何帮助将不胜感激,我已经花了 4 个小时在 15 行上试图让它工作。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#define HISTORY_COUNT 20
char *prompt = "% ";

int
main()
{
int pid;
//int child_pid;
char line[81];
char *token;
char *separator = " \t\n";
char **args;
char **args2;
char **args3;
char cmd[100];
char *hp;
char *cp;
char *ifile;
char *ofile;
int check;
int pfds[2];
int i;
int j;
int current = 0;
int p = 0;
//int check;
char *hist[HISTORY_COUNT];
//char history[90];
//typedef void (*sighandler_t) (int);

args = malloc(80 * sizeof(char *));
args2 = malloc(80 * sizeof(char *));

signal(SIGINT, SIG_IGN);

while (1) {
    fprintf(stderr, "%s", prompt);
    fflush(stderr);

    if (fgets(line, 80, stdin) == NULL)
        break;


    // split up the line

    i = 0;
    while (1) {
        token = strtok((i == 0) ? line : NULL, separator);
        if (token == NULL)
            break;
        args[i++] = token;

             /* build command array */

    }
    args[i] = NULL;

    if (i == 0){
        continue;
    }

    // assume no redirections
    ofile = NULL;
    ifile = NULL;

    // split off the redirections
    j = 0;
    i = 0;
    while (1) {        //whosebug.com/questions/35569673
        cp = args[i++];
        if (cp == NULL)
            break;

        switch (*cp) {
        case '<':
            if (cp[1] == 0)
                cp = args[i++];
            else
                ++cp;
            ifile = cp;
            break;

        case '>':
            if (cp[1] == 0)
                cp = args[i++];
            else
                ++cp;
            ofile = cp;
            break;
    case '|':

        if(cp[1] ==0){
           cp = args[i++];
           if(pipe(pfds) == -1){
               perror("Broken Pipe");
               exit(1);
           }
       p = 1;
    }
    else{ 
       ++cp;

    }
       break;

        default:
            args2[j++] = cp;
        args3[cp++] = cp
            break;
        }
    }
    args2[j] = NULL;
    if (j == 0)
        continue;

    switch (pid = fork()) {
        case 0:
            // open stdin
            if (ifile != NULL) {
                int fd = open(ifile, O_RDONLY);

                if (dup2(fd, STDIN_FILENO) == -1) {
                    fprintf(stderr, "dup2 failed");
                }

                close(fd);
            }


            // open stdout
            if (ofile != NULL) {
                // args[1] = NULL;
                int fd2;


                if ((fd2 = open(ofile, O_WRONLY | O_CREAT, 0644)) < 0) {
                    perror("couldn't open output file.");
                    exit(0);
                }

                // args+=2;
                printf("okay");
                dup2(fd2, STDOUT_FILENO);
                close(fd2);
            }


        if(p == 1){        //from whosebug.com/questions/2784500
        close(1);
        dup(pfds[1]);
        close(pfds[0]);
        execvp(args2[0], args2);
        break;
        }


        if(strcmp(args2[0], "cd") == 0){            //cd command
            if(args2[1] == NULL){
                fprintf(stderr, "Expected argument");
            }
            else{

            check = chdir(args2[1]);

        if(check != 0){
            fprintf(stderr,"%s",prompt);

            }
           }
        break;
        }

        execvp(args2[0], args2);        /* child */
        signal(SIGINT, SIG_DFL);
        fprintf(stderr, "ERROR %s no such program\n", line);
        exit(1);
        break;

    case -1:
        /* unlikely but possible if hit a limit */
        fprintf(stderr, "ERROR can't create child process!\n");
        break;

    default:
        //printf("am I here");
    if(p==1){
        close(0);
        dup(pfds[0]);
        close(pfds[1]);
        //execvp();
    }
        wait(NULL);
        //waitpid(pid, 0, 0);
    }
}

exit(0);

}

我添加了一个单独的参数传递来捕获和记住 I/O 重定向,并将它们从传递给 child 的参数列表中删除。

这是更正后的代码[请原谅不必要的样式清理]:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

char *prompt = "% ";

int
main()
{
    int pid;
    //int child_pid;
    char line[81];
    char *token;
    char *separator = " \t\n";
    char **args;
    char **args2;
    char *cp;
    char *ifile;
    char *ofile;
    int i;
    int j;
    int err;
    //int check;
    //char history[90];
    //typedef void (*sighandler_t) (int);

    args = malloc(80 * sizeof(char *));
    args2 = malloc(80 * sizeof(char *));

    //signal(SIGINT, SIG_IGN);

    while (1) {
        fprintf(stderr, "%s", prompt);
        fflush(stderr);

        if (fgets(line, 80, stdin) == NULL)
            break;

        // split up the line
        i = 0;
        while (1) {
            token = strtok((i == 0) ? line : NULL, separator);
            if (token == NULL)
                break;
            args[i++] = token;              /* build command array */
        }
        args[i] = NULL;
        if (i == 0)
            continue;

        // assume no redirections
        ofile = NULL;
        ifile = NULL;

        // split off the redirections
        j = 0;
        i = 0;
        err = 0;
        while (1) {
            cp = args[i++];
            if (cp == NULL)
                break;

            switch (*cp) {
            case '<':
                if (cp[1] == 0)
                    cp = args[i++];
                else
                    ++cp;
                ifile = cp;
                if (cp == NULL)
                    err = 1;
                else
                    if (cp[0] == 0)
                        err = 1;
                break;

            case '>':
                if (cp[1] == 0)
                    cp = args[i++];
                else
                    ++cp;
                ofile = cp;
                if (cp == NULL)
                    err = 1;
                else
                    if (cp[0] == 0)
                        err = 1;
                break;

            default:
                args2[j++] = cp;
                break;
            }
        }
        args2[j] = NULL;

        // we got something like "cat <"
        if (err)
            continue;

        // no child arguments
        if (j == 0)
            continue;

        switch (pid = fork()) {
        case 0:
            // open stdin
            if (ifile != NULL) {
                int fd = open(ifile, O_RDONLY);

                if (dup2(fd, STDIN_FILENO) == -1) {
                    fprintf(stderr, "dup2 failed");
                }

                close(fd);
            }

            // trying to get this to work
            // NOTE: now it works :-)
            // open stdout
            if (ofile != NULL) {
                // args[1] = NULL;
                int fd2;

                //printf("PLEASE WORK");
                if ((fd2 = open(ofile, O_WRONLY | O_CREAT, 0644)) < 0) {
                    perror("couldn't open output file.");
                    exit(0);
                }

                // args+=2;
                printf("okay");
                dup2(fd2, STDOUT_FILENO);
                close(fd2);
            }

            execvp(args2[0], args2);        /* child */
            signal(SIGINT, SIG_DFL);
            fprintf(stderr, "ERROR %s no such program\n", line);
            exit(1);
            break;

        case -1:
            /* unlikely but possible if hit a limit */
            fprintf(stderr, "ERROR can't create child process!\n");
            break;

        default:
            //printf("am I here");
            wait(NULL);
            //waitpid(pid, 0, 0);
        }
    }

    exit(0);
}

更新:

If you're still around do you think you could help me with creating a pipe?

当然可以。 post 这里太大了。参见:http://pastebin.com/Ny1w6pUh


Wow did you create all 3300 lines?

是的。

我从我的另一个 SO 答案中借用了 xstr [错误修复和增强]。 dlk 是新的,但我做了很多,所以很容易。其中大部分是新代码。

但是...它是由fragments/concepts我以前做过的:tgb、FWD、BTV、sysmagic组成的。请注意 struct foo 的所有结构成员都带有前缀 foo_ [对我来说是标准]。宏 "trickery" 与 DLHDEFDLKDEF 来模拟 inheritance/templates 也是我 [在必要时] 做的事情。

许多函数变量重用了我的风格:idx 用于索引变量 [我永远不会使用 i/j,而是 xidx/yidx],cp 用于字符指针,cnt 用于计数,len 用于字节长度等。因此,我不必 "think" 关于小东西 [战术],可以专注于策略。

以上idx等。阿尔。对我来说是 "signature style"。它不一定比其他人更好 [或更坏]。这是因为当 linker/loader 只能处理 8 个字符符号时我开始使用 C,因此简洁是关键。但是,我习惯了使用较短的名字。考虑哪个是 clearer/better:

for (fooidx = 0;  fooidx <= 10;  ++fooidx)

或者:

for (indexForFooArray = 0;  indexForFooArray <= 10;  ++indexForFooArray)

我使用 do { ... } while (0) 来避免 if/else 阶梯 很多 。这称为 "once through" 循环。这被认为是 "controversial",但是,根据我的经验,它使代码更清晰。就个人而言,我从来没有发现 do/while 循环的 [更标准] 使用不能用 whilefor 循环完成更多 easily/better -- YMMV .事实上,许多语言甚至根本没有 do/while

此外,我使用小写字母,除非它是 #define [或 enum],它总是大写的。也就是说,我使用"snake case"(例如fooidx)和而不是 "camel hump case"(例如indexForFooArray)。

包含函数原型的 .proto 文件是 auto-generated。这是一个 巨大 的节省时间。 旁注: 确保您至少有来自外部 link 的 v2,因为 Makefile 中存在错误。 make clean 会擦除 .proto。 v2 不会那样做

多年来我形成了自己的风格。结果 linux 内核样式是 "borrowed from mine"。实际上不是 :-) 我的排在第一位。但是......他们同时提出了与我的 99% 匹配的东西:/usr/src/kernels/whatever_version/Documentation/CodingStyle.

与特定风格[自己的]保持一致是关键。对于给定的函数,您不必担心变量的名称、使用的缩进或空行。

这有助于 reader/new 开发人员。他们可以读几个函数,看看游戏中的风格,然后走得更快,因为所有的函数都有相似的风格。

所有这一切让您 "go faster" 并且在第一次尝试时仍然获得高质量的代码。我也相当有经验。

此外,我的代码注释集中在"intent"。也就是说,您希望代码在现实世界中做什么。他们应该回答 "what/why",密码是 "how".