fgets()调用重定向获取exception数据流

我正打算用C语言编写一个shell。 以下是源代码:

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

当我执行编译后的可执行文件时,将stdin重定向到名为t.sh的文件,其内容为“1111 \ n2222 \ n”,如./myshell <t.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),而t.sh中只有2行。 当在t.sh中添加更多行时,这些情况会变得更糟。

主进程是执行getcmd的唯一进程,我们可以通过pid的输出来判断。

顺便说一句,我发现如果删除了代码行wait(&r),输出就可以正常了。

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

 % strace -f ./a.out [lots of stuff] wait4(-1, strace: Process 29317 attached  [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在fork之后自动执行此操作。 我也看到孩子们正在冲洗stdout

我不确定该怎么想…但很明显,如果你想编写一个shell,你根本不能 进行标准流交互。 如果lseek 没有发生,那么子进程将看到跳过最多4095字节的stdin ! 您必须始终使用 write 。 或者,在从stdin读取任何内容之前,您可能会将以下调用添加到main的开头:

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

这会将stdin流设置为无缓冲模式 ,因此不应该读太多。 不过, fgets的Linux手册页说:

不建议将stdio库中的输入函数调用与对输入流关联的文件描述符的read(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 

但自然会使另一个问题变得明显 – 孩子看到输入被跳过了。


PS

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

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