繁体   English   中英

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

[英]Implementing shell in C and need help handling input/output redirection

第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

'<'和'>'都有效,但在执行后没有输出。


第1轮

我已经在C中使用了一个相对简单的shell一段时间了,但我在实现input(<)和output(>)重定向时遇到了麻烦。 帮我找到以下代码中的问题:

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

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

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

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的标准输入,你必须在执行覆盖输出的dup2()之前执行dup() 但是,最好不要改变父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()调用。

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

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

这是正在发生的事情。 在调用fork() ,有两个进程正在执行,这些进程是原始进程的重复。 不同之处在于fork()的返回值,它存储在pid

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

你可以修复这个bug,但你可以做得更好。 你实际上不必重定向父输出,只是孩子的输出。 所以先简单检查一下pid 然后也没有必要恢复任何文件描述符。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM