【问题标题】:Input Redirection in C - Using fgets to read from stdin not workingC中的输入重定向-使用fgets从stdin读取不起作用
【发布时间】:2021-09-17 08:18:03
【问题描述】:

我有一个任务,我需要在 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...

【问题讨论】:

  • edit您的问题并添加更多关于我们如何重现问题的详细信息。显示您使用的输入(text.txt 的内容)、实际和预期输出,并在必要时描述实际输出错误的原因。
  • @Bodo 感谢您的反馈。我按要求更新了问题。
  • 我无法重现问题,我得到了预期的输出。见onlinegdb.com/DVz2ZNk0Q
  • 您是否准确地测试了问题中的代码?我不知道为什么它的行为可能会有所不同。我不确定,但我认为在reset() 中你应该在dup2(saved_stdin, STDIN_FILENO); 之前close(STDIN_FILENO)。除此之外,我建议检查所有返回值是否有错误指示。并且没有参数的函数应该写成void echo(void);等(C不是C++)
  • 当您将参数传递给您的程序时,您是否正确地转义了命令行中的&lt;?因为如果你不这样做,它会被你的命令 shell 解释而不是传递给你的程序。

标签: c io stdin fgets


【解决方案1】:

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

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

您也可以使用标准 API 重定向 stdin,我建议在一般情况下这样做。使用fopenfreopen(用于将文件附加到stdin)、fgetsfclose

【讨论】:

    【解决方案2】:

    对于您的问题的答案,请阅读至此答案的末尾,因为我必须对您的代码发表更多评论。

    首先,您在代码中混合常量标识符和值本身,只是为了混淆您的读者?

        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,那么你需要包含&lt;sys/stat.h&gt;,而你不需要。

    另外,如果您将main() 声明为int main(void),请将void echo(void)void reset(void) 声明为void echo()void reset() 意味着编译器应该允许您将echo() 传递给未定义的参数列表(用于传递变量参数的传统语法),如果您更改接口,它不会检测到可能的错误。

    重定向代码应该没问题,除了不检查dup()dup2() 返回值之类的小问题。在我的系统 (FreeBSD 13.0) 中,stdin 在标记 EOF 后放弃为您提供更多输入,(现在 Linux 也会发生这种情况)以便能够继续从中读取(记住 stdio 是一个库,而不是一组系统调用,如open()close()),一旦在函数echo() 中检测到stdin 中的EOF,您应该调用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

    【讨论】:

      【解决方案3】:

      stdio 和重定向不能混用。您复制了文件描述符,但 stdio 会查看 FILE*,它不知道这些操作。一旦stdin 位于feof,它就位于feof

      使用read 而不是fgets(不要忘记对结果缓冲区进行空终止,记住它不是字符串)。或者,您可以尝试将标准输入缓冲模式设置为_IONBF 和/或每次dup 对应fd 时调用clearerr,但我没有尝试过。

      【讨论】:

      • 这不是真的。 stdio 与重定向完美结合。在重定向之前您必须小心刷新缓冲区,但它们可以完美混合。看我的回答。
      • @LuisColorado 您无法刷新输入缓冲区。
      • 是的,你可以。刷新 iinput 缓冲区只是删除输入缓冲区中缓冲的所有字符,使下一次读取再次从输入文件描述符中读取。但是要刷新输入缓冲区,您必须调用fpurge()
      • @LuisColorado C 标准说刷新输入缓冲区是未定义的行为。
      • 我还没有提到fflush() 我已经说过关于刷新缓冲区的内容,使用fflush() 处理输出缓冲区,使用fpurge() 处理输入缓冲区。请阅读我的 cmets,不要以为我只是在谈论 fflush()
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多