2017-08-13 36 views
6

我正要用C语言编写一个shell。这是下面的代码:fgets()调用重定向获取异常数据流

#include <unistd.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/wait.h> 
#include <stdlib.h> 

int 
getcmd(char *buf, int nbuf) 
{ 
    memset(buf, 0, nbuf); 
    fgets(buf, nbuf, stdin); 
    printf("pid: %d, ppid: %d\n", getpid(), getppid()); 
    printf("buf: %s", buf); 
    if(buf[0] == 0) {// EOF 
    printf("end of getcmd\n"); 
    return -1; 
    } 
    return 0; 
} 

int 
main(void) 
{ 
    static char buf[100]; 
    int fd, r, ret; 

    // Read and run input commands. 
    while((ret = getcmd(buf, sizeof(buf))) >= 0){ 
    if(fork() == 0) 
     exit(0); 
    wait(&r); 
    } 
    exit(0); 
} 

当我执行编译的可执行文件:标准输入重定向到一个名为t.sh文件,该文件的内容是“1111 \ n2222 \ n”个像./myshell<吨。 SH,输出为:

pid: 2952, ppid: 2374 
buf: 1111 
pid: 2952, ppid: 2374 
buf: 2222 
pid: 2952, ppid: 2374 
buf: 2222 
pid: 2952, ppid: 2374 
buf: end of getcmd 

显然,函数getcmd()得到3行(1111,2222,2222),而仅存在2 t.sh.线在t.sh中放置更多行时,这些情况会变得更糟。

而主进程是执行getcmd的唯一进程,我们可以通过pid的输出来判断。顺便说一下,我发现如果等待的代码行(&r)被移除,输出就会正常。

+0

其实很有趣。我可以在* 4.10.0-19-generic#21上重现行为 - Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux *。当从文件重定向时,似乎每次都会重置文件指针。 –

+0

这可能解释代码':)'中未使用的'int fd'' –

+0

我可以在Ubuntu 16.04 LTS上重现问题;它不会在macOS Sierra 10.12.6上重现。它在某种程度上反映了Linux库中的一个错误,但这很奇怪 - 它的另一种选择是Linux中编译器的错误。我不得不修改代码来关注Linux上'fgets()'的返回值;我用'-Werror'进行编译,并被告知'忽略'fgets'的返回值,用属性warn_unused_result'声明。 (macOS头文件不使用该功能。)我还必须处理各种其他警告(未使用的变量,变量设置但未使用等) –

回答

5

wait确保子进程获得时间在父文件完成文件之前运行。如果我strace Linux下的文件,我得到

% strace -f ./a.out 
[lots of stuff] 
wait4(-1, strace: Process 29317 attached 
<unfinished ...> 
[pid 29317] lseek(0, -2, SEEK_CUR)  = 0 
[pid 29317] exit_group(0)    = ? 
[pid 29317] +++ exited with 0 +++ 
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29317 
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=29317, si_uid=1000, si_status=0 
    _utime=0, si_stime=0} --- 
[lots of stuff] 

子进程倒带标准输入为fork后的第一个操作中的一个,在这之后会及时退出。具体来说,它将从流中读取的数据字节倒回到fgets缓冲区中,但仍未使用libc后叉自动执行。我还看到冲洗stdout

我不知道该怎么思考这个问题......但很显然,如果你想写一个shell,你不得与标准流与<stdio.h>在所有互动。如果lseek没有发生,那么子进程将会看到高达4095字节的stdin被跳过!您必须始终使用readwrite而不是<unistd.h>。或者,你可能有一些运气添加以下调用到的main开始从stdin读取之前什么:

if (setvbuf(stdin, NULL, _IONBF, 0) != 0) { 
    perror("setvbuf:"); 
    exit(1); 
} 

这将stdin流设置为缓冲模式,所以它不应该过度解读许多。尽管如此,Linux manual page for fgets说:

这是不可取的输入函数的调用从低层次的 stdio库混合调用阅读(2)与输入流相关的文件 描述;结果 将是未定义的,很可能不是你想要的。

顺便说一句,这不能如果stdin来自一个管道,而不是复制:

% echo -e '1\n2' | ./a.out 
pid: 498, ppid: 21285 
buf: 1 
pid: 498, ppid: 21285 
buf: 2 
pid: 498, ppid: 21285 
buf: end of getcmd 

但自然,使其他问题可见 - 那孩子看到输入被跳过。


P.S.

您从不检查返回值fgets,因此您不知道何时发生读取错误。

如果操作期间发生了读取错误数组内容是不确定的,则返回空指针。

+2

为什么libc C的fgets在阅读后倒带? –

+0

@ afr0ck我相信这是一个糟糕的尝试,使父母和孩子看到文件指针的位置,它将被放置在那里没有缓冲。但是我无法找到有关此*的文档*。 –

+2

这听起来像是一个bug。我无法想象这个孩子寻求的借口。这是无根据的干扰。它不能在其他系统上重现(特别是,macOS Sierra 10.12.6)。但是,对发生的事情有良好的分析。做得好! –