C 语言中的输入重定向 - 使用 fgets 从 stdin 读取不起作用



我有一个作业,我需要在 C 语言中创建一个 shell。其中一个规范是在输入重定向符号("<")时实现输入重定向。然后我尝试使用fgetsstdin读取输入。读取输入后,saved_stdin将恢复为正常的输入流。

但是,在第一次重定向输入时,它可以完美运行。然后,在那之后的一段时间里,没有从stdin那里读到任何东西.我已经尝试了几次调试,但找不到我的错误。非常感谢您的帮助!

我的代码在下面,以便您可以重现错误,并帮助我纠正错误。此外,请确保在运行程序的目录中创建一个名为text.txt的文件。text.txt内容:

hello, how are you?

请注意,代码非常简化,我只是使用硬编码参数。在我的实际实现中,输入是由用户给出的(我 100% 确定接收输入没有错)。

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#define MAX_ARG_LENGTH 100
int redir_in(char **args, int in_pos);
void echo();
void reset();
// Global Variables
int saved_stdin;
bool changed_in;
int main(void)
{
// sample arguments (args)
char *args[MAX_ARG_LENGTH] = {"echo", "<", "text.txt"};
// position of '<'
int pos = 1;
printf("output 1: ");
if(redir_in(args, pos) == 0){
echo();
reset();
}

printf("noutput 2: ");
if(redir_in(args, pos) == 0){
echo();
reset();
}
printf("nexiting...n");
return 0;
}
void echo()
{
char line[MAX_ARG_LENGTH];
while(fgets(line, MAX_ARG_LENGTH, stdin) != NULL)
printf("%s", line);
}
// args is an array of inputted string arguments, in_pos is the index 
// of '<' in the array
int redir_in(char **args, int in_pos)
{
char *file_name;
file_name = args[in_pos + 1];
int fd;
fd = open(file_name, O_RDONLY, S_IRUSR);
if (fd < 0)
return -1;
saved_stdin = dup(0);
dup2(fd, STDIN_FILENO);
close(fd);
changed_in = true;
return 0;
}
void reset()
{
if(changed_in){
dup2(saved_stdin, 0);
close(saved_stdin);
changed_in = false;
}
}

预期产出:

output 1: hello, how are you?
output 2: hello, how are you?
exiting...

我得到的输出:

output 1: hello, how are you?
output 2: 
exiting...

对于您的问题的答案,请阅读本答案的末尾,因为我必须对您的代码进行更多评论。

首先,您在代码中混合常量标识符和值本身,只是为了让读者感到困惑?

saved_stdin = dup(0);
dup2(fd, STDIN_FILENO);

或者你写:

saved_stdin = dup(STDIN_FILENO);
dup2(fd, STDIN_FILENO);

saved_stdin = dup(0);
dup2(fd, 0);

请保持统一和连贯。

其次,如果你使用S_IFRUSR,那么你需要包括<sys/stat.h>,而你没有。

此外,如果您将main()声明为int main(void),请对void echo(void)void reset(void)执行相同的操作,void echo()void reset()意味着编译器应该允许您传递echo()未定义的参数列表(变量参数传递的旧语法),并且在您更改接口时它不会检测到可能的错误。

重定向代码应该没问题,除了不检查dup()dup2()返回值等小事情。 在我的系统 (FreeBSD 13.0) 中,一旦它标记了EOFstdin放弃给你更多的输入 (现在也发生在 linux) 所以为了能够继续从中读取 (记住 stdio 是一个库,而不是像open()close()这样的一组系统调用),一旦你在函数echo()中检测到 EOFstdin,你应该调用clearerr(stdin);

void echo(void)
{
char line[MAX_ARG_LENGTH];
while(fgets(line, MAX_ARG_LENGTH, stdin) != NULL)
printf("%s", line);
clearerr(stdin);   /* <--- this call allows you to read again after EOF */
}

这就是stdin重定向回后不阅读其他任何内容的原因。 我在两个if语句之间包含一个调用echo();,以检查重定向是否回到saved_stdin

你在这里混合了两个不同的API——UnixAPI处理int文件描述符(opendupdup2close0用于标准输入)和C标准API处理FILE *句柄(fgetsstdin用于标准输入)。

这通常是一个坏主意。使它们相互协作是可能的,但它很脆弱,并且(不必要地)依赖于平台。

您也可以使用标准 API 重定向stdin,我建议使用一般条款。使用fopenfreopen(用于将文件附加到stdin)、fgetsfclose

stdio和重定向不会混合使用。你把你的文件描述符弄乱了,但 stdio 会查看FILE*,它对这些操作一无所知。一旦stdinfeof,它就在feof

使用read而不是fgets(不要忘记以 null 终止生成的缓冲区,请记住它不是字符串)。或者,您可以尝试将 stdin 缓冲模式设置为_IONBF和/或每次dup相应的fd时调用clearerr,但我还没有尝试过。