在C中实现shell并需要帮助处理输入/输出重定向

时间:2012-07-17 02:53:28

标签: c linux shell

第2轮

在阅读了一些答案之后,我修改后的代码是:

int pid = fork();

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {   

    if (in) { //if '<' char was found in string inputted by user
        int fd0 = open(input, O_RDONLY, 0);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
        in = 0;
    }

    if (out) { //if '>' was found in string inputted by user
        int fd1 = creat(output, 0644);
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
        out = 0;
    }   

    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    free(res);
}

它可以工作,但似乎标准输出没有被重新连接或者那种效果。这是执行:

SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output

'&LT;'和'&gt;'两者都有效,但在执行后没有输出。


第1轮

我已经在C中使用相对简单的shell一段时间了,但是我在实现输入(&lt;)和输出(&gt;)重定向时遇到了麻烦。帮我找到以下代码中的问题:

int fd;
int pid = fork();
int current_out;

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_out = dup(0);
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {       
    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    dup2(current_out, 1);
    free(res);
}

我可能会有一些不必要的材料,因为我一直在尝试不同的东西让它发挥作用。我不确定出了什么问题。

3 个答案:

答案 0 :(得分:15)

重定向后,您有太多文件描述符打开。让我们分析这两段:

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_in = dup(0);  // Fix for symmetry with second paragraph
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

我将成为慈善机构并忽视你忽略错误这一事实。但是,您需要错误检查系统调用。

在第一段中,您打开一个文件并在变量fd中捕获文件描述符(可能是3)。然后,您通过标准输入(STDIN_FILENO)复制文件描述符。但请注意,文件描述符3仍处于打开状态。然后你做一个dup(0)(为了保持一致,应该是STDIN_FILENO),得到另一个文件描述符,也许是4.所以你有文件描述符0,3和4指向同一个文件(和,实际上,相同的打开文件描述 - 注意打开的文件描述与打开的文件描述符不同。如果您对current_in的意图是保留(父)shell的标准输入,则必须在执行覆盖输出的dup()之前执行dup2()。但是,最好不要改变父shell的文件描述符;它比重新复制文件描述符的开销更小。

然后你或多或少地重复第二段中的过程,首先用fd = creat(...)调用覆盖文件描述符3的唯一记录但是获得一个新的描述符,可能是5,然后通过标准输出复制它。然后你做一个dup(1),产生另一个文件描述符,可能是6。

因此,您将主shell的stdin和stdout重定向到文件(并且无法将这些文件恢复为原始值)。因此,您的首要问题是您在fork()之前正在进行重定向;您应该在fork()之后执行此操作 - 但是当您在进程之间进行管道连接时,您将需要在分叉之前创建管道。

你的第二个问题是你需要关闭过多的文件描述符,其中一个你不再有参考文献描述符。

所以,您可能需要:

if ((pid = fork()) < 0)
    ...error...
else if (pid == 0)
{
    /* Be childish */
    if (in)
    {
        int fd0 = open(input, O_RDONLY);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
    }

    if (out)
    {
        int fd1 = creat(output , 0644) ;
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
    }
    ...now the child has stdin coming from the input file, 
    ...stdout going to the output file, and no extra files open.
    ...it is safe to execute the command to be executed.
    execve(cmd[0], cmd, env);   // Or your preferred alternative
    fprintf(stderr, "Failed to exec %s\n", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

在执行任何此操作之前,您应该确保已经刷新了shell的标准I / O通道,可能是使用fflush(0),这样如果分叉的子进程由于问题而写入标准错误,没有多余的重复输出。

另请注意,应对错误检查各种open()次调用。

答案 1 :(得分:7)

重定向后,您有太多文件描述符打开。你需要的代码就是这个。

    if (pid == 0)
{          /* for the child process:         */

    // function for redirection ( '<' , '>' )

    int fd0,fd1,i,in=0,out=0;
    char input[64],output[64];

    // finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that

    for(i=0;argv[i]!='\0';i++)
    {
        if(strcmp(argv[i],"<")==0)
        {        
            argv[i]=NULL;
            strcpy(input,argv[i+1]);
            in=2;           
        }               

        if(strcmp(argv[i],">")==0)
        {      
            argv[i]=NULL;
            strcpy(output,argv[i+1]);
            out=2;
        }         
    }

    //if '<' char was found in string inputted by user
    if(in)
    {   

        // fdo is file-descriptor
        int fd0;
        if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
            perror("Couldn't open input file");
            exit(0);
        }           
        // dup2() copies content of fdo in input of preceeding file
        dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0 

        close(fd0); // necessary
    }

    //if '>' char was found in string inputted by user 
    if (out)
    {

        int fd1 ;
        if ((fd1 = creat(output , 0644)) < 0) {
            perror("Couldn't open the output file");
            exit(0);
        }           

        dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
        close(fd1);
    }

    execvp(*argv, argv);
    perror("execvp");
    _exit(1);

    // another syntax
    /*      if (!(execvp(*argv, argv) >= 0)) {     // execute the command  
            printf("*** ERROR: exec failed\n");
            exit(1);
     */ 
}


    else if((pid) < 0)
    {     
        printf("fork() failed!\n");
        exit(1);
    }

    else {                                  /* for the parent:      */

        while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors) 
    }
}

答案 2 :(得分:4)

这是发生了什么。调用fork()后,执行的两个进程与原始进程重复。不同之处在于fork()的返回值,该值存储在pid

然后,两个进程(shell和子进程)将stdin和stdout重定向到相同的文件。我认为你试图在current_out中保存以前的fd,但正如Seth Robertson指出的那样,这当前不起作用,因为正在保存错误的文件描述符。父母也恢复了它的标准输出,但不是标准输入。

您可以修复此错误,但您可以做得更好。你实际上不必重定向父输出,只是孩子的输出。所以只需先查看pid即可。然后也没有必要恢复任何文件描述符。