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
的返回值,因此您不知道何时发生读取错误。
如果在操作期间发生读取错误 , 则数组内容是不确定的,并返回空指针。