我正打算用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),输出就可以正常了。
答案 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
的返回值,因此您不知道何时发生读取错误。
如果在操作期间发生读取错误,则数组内容不确定并返回空指针。