带重定向的fgets()调用获取异常数据流
fgets() call with redirection get abnormal data stream
我正准备用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);
}
当我执行已编译的可执行文件并将标准输入重定向到名为 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
确保 child 进程在 parent 完成文件之前有时间到达 运行。如果我 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]
child 进程 倒回 标准输入作为 fork
之后的第一个操作之一,之后它将立即退出。具体来说,它将 fgets
从流中读入的字节数倒回到缓冲区中,但仍然 未使用 。 libc 在 fork 后自动执行此操作。我还看到 child 进程刷新 stdout
.
我不知道该怎么想...但很明显,如果你想写一个shell,你一定不能与标准流交互<stdio.h>
。如果 lseek
没有 发生,那么 child 进程将看到多达 4095 个字节的 stdin
被跳过!您必须始终仅使用 <unistd.h>
中的 read
和 write
。或者,在从 stdin
:
读取任何内容之前,将以下调用添加到 main
的开头可能会很幸运
if (setvbuf(stdin, NULL, _IONBF, 0) != 0) {
perror("setvbuf:");
exit(1);
}
这会将 stdin
流设置为 非缓冲模式 ,因此不应读取太多。然而,Linux manual page for fgets
说:
It is not advisable to mix calls to input functions from the
stdio library with low-level calls to read(2) for the file
descriptor associated with the input stream; the results
will be undefined and very probably not what you want.
顺便说一句,如果 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
但这自然会使另一个问题可见 - child 看到输入被跳过。
P.S.
您从不检查 fgets
的 return 值,因此您不知道何时发生读取错误。
If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.
我正准备用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);
}
当我执行已编译的可执行文件并将标准输入重定向到名为 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
确保 child 进程在 parent 完成文件之前有时间到达 运行。如果我 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]
child 进程 倒回 标准输入作为 fork
之后的第一个操作之一,之后它将立即退出。具体来说,它将 fgets
从流中读入的字节数倒回到缓冲区中,但仍然 未使用 。 libc 在 fork 后自动执行此操作。我还看到 child 进程刷新 stdout
.
我不知道该怎么想...但很明显,如果你想写一个shell,你一定不能与标准流交互<stdio.h>
。如果 lseek
没有 发生,那么 child 进程将看到多达 4095 个字节的 stdin
被跳过!您必须始终仅使用 <unistd.h>
中的 read
和 write
。或者,在从 stdin
:
main
的开头可能会很幸运
if (setvbuf(stdin, NULL, _IONBF, 0) != 0) {
perror("setvbuf:");
exit(1);
}
这会将 stdin
流设置为 非缓冲模式 ,因此不应读取太多。然而,Linux manual page for fgets
说:
It is not advisable to mix calls to input functions from the stdio library with low-level calls to read(2) for the file descriptor associated with the input stream; the results will be undefined and very probably not what you want.
顺便说一句,如果 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
但这自然会使另一个问题可见 - child 看到输入被跳过。
P.S.
您从不检查 fgets
的 return 值,因此您不知道何时发生读取错误。
If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.