我有一个作业,我需要在 C 语言中创建一个 shell。其中一个规范是在输入重定向符号("<")时实现输入重定向。然后我尝试使用fgets
从stdin
读取输入。读取输入后,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) 中,一旦它标记了EOF
,stdin
放弃给你更多的输入 (现在也发生在 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
文件描述符(open
,dup
,dup2
,close
,0
用于标准输入)和C标准API处理FILE *
句柄(fgets
,stdin
用于标准输入)。
这通常是一个坏主意。使它们相互协作是可能的,但它很脆弱,并且(不必要地)依赖于平台。
您也可以使用标准 API 重定向stdin
,我建议使用一般条款。使用fopen
、freopen
(用于将文件附加到stdin
)、fgets
和fclose
。
stdio
和重定向不会混合使用。你把你的文件描述符弄乱了,但 stdio 会查看FILE*
,它对这些操作一无所知。一旦stdin
在feof
,它就在feof
。
使用read
而不是fgets
(不要忘记以 null 终止生成的缓冲区,请记住它不是字符串)。或者,您可以尝试将 stdin 缓冲模式设置为_IONBF
和/或每次dup
相应的fd
时调用clearerr
,但我还没有尝试过。