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

时间:2017-08-13 03:26:05

标签: c gets multiprocess

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

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

当我执行已编译的可执行文件时,将stdin重定向到名为t.sh的文件,其内容为“1111 \ n2222 \ n”,如./myshell&lt; 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(&amp; r),输出就可以正常了。

1 个答案:

答案 0 :(得分: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 在fork之后自动执行此操作。我还看到 child 进程正在刷新stdout

我不确定该怎么想...但很明显,如果你想编写一个shell,你一定不能 <stdio.h>的标准流进行交互 根本。如果lseek 未发生,则子进程最多会看到跳过stdin的4095个字节!您必须始终只使用read中的write<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的返回值,因此您不知道何时发生读取错误。

  

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