如何将程序输出重定向为其输入

9

我为教学目的编写了一个简单的C++程序。我的目标是将其无限循环。

#include <iostream>
#include <string>

int main()
{
  std::cout << "text";

  for(;;) {
    std::string string_object{};
    std::getline(std::cin, string_object);
    std::cout << string_object;
  }

  return 0;
}

编译后,我按照如下方式运行程序: ./bin 0>&1 我希望发生的情况是,“文本”将输出到标准输出(stdout),现在它也会成为程序的标准输入(stdin)并一直循环。为什么会这样呢?


3
这不是 I/O 流的工作原理。在终端写入并不会将数据放到键盘输入中。 - Barmar
1
你可以使用 std::stringstream 来完成这样的事情。 - πάντα ῥεῖ
2
你为什么想要这样做呢?听起来很有趣,我也喜欢这个问题,但我很好奇是否有实际的用例。 - klutt
2
但是stdin是一个输入流,你不能使用>来重定向它,你应该使用<。但这是同样的问题。 - Barmar
1
@ziemowit141 我认为这可以帮助你。甚至可能是这个的重复。https://dev59.com/cms05IYBdhLWcg3wNPI3 如果这是你要找的,请告诉我。 - klutt
显示剩余12条评论
3个回答

2
首先,在使用std::cout输出时需要输出换行符,否则std::getline()将无法读取完整的一行。
改进版本:
#include <iostream>
#include <string>

int main()
{
  std::cout << "stars" << std::endl;

  for(;;) {
    std::string string_object;
    std::getline(std::cin, string_object);
    std::cout << string_object << std::endl;
  }

  return 0;
}

现在尝试这个:
./bin >file <file

你没有看到任何输出,因为它被写入了文件中。但是如果你停止程序并查看文件,哇,它就被填满了。

stars
stars
stars
stars

同时,当你尝试时反馈循环无法开始的原因是

./bin 0>&1

问题在于,你最终会发现stdin和stdout都连接到了/dev/tty(这意味着你可以看到输出)。

但是TTY设备永远无法关闭循环,因为它实际上由两个独立的通道组成,一个将输出传递到终端,另一个将终端输入传递给进程。

如果您使用常规文件进行输入和输出,则可以关闭循环。只要进程的stdin与该文件连接,每个写入文件的字节也将从中读取。前提是没有其他进程同时从文件中读取,因为流中的每个字节只能被读取一次。


我修正了关于为什么需要输出std::endl的解释(这样getline()才能看到已终止的行;与行缓冲无关) - kisch
我删除了我的第一条评论,将其放在答案中,以更全面地回答问题。 - kisch
固定说明TTY设备的特殊行为(感谢@user414777) - kisch
非常感谢您的努力。您的示例非常精确,这是我完全理解问题的关键信息:“但TTY设备永远不能关闭循环,因为它实际上由两个单独的通道组成,一个将输出传递到终端,另一个将终端输入传递给进程”。 - ziemowit141

0
因为标准输出和标准输入不会创建一个循环。它们可能指向同一个tty,但是tty实际上是两个独立的通道,一个用于输入,一个用于输出,它们不会相互回路。
您可以尝试通过将程序的标准输入连接到管道的读取端口,并将其标准输出连接到其写入端口来创建循环。这将适用于cat
mkfifo fifo
{ echo text; strace cat; } <>fifo >fifo
...
read(0, "text\n", 131072)               = 5
write(1, "text\n", 5)                   = 5
read(0, "text\n", 131072)               = 5
write(1, "text\n", 5)                   = 5
...

但不是因为你的程序。这是因为你的程序试图读取,但它的写入没有以换行符结尾。修复这个问题并将读取的行打印到stderr(这样我们就不必使用strace来证明你的程序中发生了什么),我们得到:

#include <iostream>
#include <string>

int main()
{
  std::cout << "text" << std::endl;

  for(;;) {
    std::string string_object{};
    std::getline(std::cin, string_object);
    std::cerr << string_object << std::endl;
    std::cout << string_object << std::endl;
  }
}

g++ foo.cc -o foo
mkfifo fifo; ./foo <>fifo >fifo
text
text
text
...

注意:使用<>fifo打开命名管道(fifo)的方式,可以同时打开其读端和写端,避免阻塞。而不是从其路径重新打开fifo,stdout可以直接从stdin复制(prog <>fifo >&0),或者可以先将fifo作为不同的文件描述符打开,然后在不阻塞的情况下分别以只读模式和只写模式打开stdin和stdout(prog 3<>fifo <fifo >fifo 3>&-)。

它们在手头的示例中都能起到相同的作用。在Linux上,:|prog >/dev/fd/0(以及echo text | strace cat >/dev/fd/0)也可以工作--无需使用mkfifo创建命名管道。


你实际上不需要一个FIFO来关闭循环。那只会让情况变得更加复杂。一个简单的文件就足够了。我在我的回答中演示了这一点。你是对的,TTY永远无法关闭循环;我应该在这方面纠正我的答案。 - kisch
在我看来,它使用了一个常规文件,这使得情况变得复杂(除了填满磁盘之外;-))。它涉及到文件指针位置的棘手问题以及对同一文件进行非同步读写的问题。 - user10678532
是的,但FIFO在输入和输出之间添加了一个中间实例。输入和输出不是直接连接的。这就是OP想要实现的。使用常规文件,您将文件内容作为一种不必要的副作用。问题要求如何直接将进程输出反馈到其输入;而常规文件是最接近的(或者我能想到的)。 - kisch
@kisch 你有点糊涂了 -- 因为你打开文件两次,实际上你正在使用 两个 引用同一个磁盘inode的文件,并且 这是一个巧合 ,使得读写的顺序符合预期。(而且,在这两个打开的文件对象之间还有大约十几层,但这不是重点)。当然,如果你使用单个文件以读写模式打开,并使用相同的文件指针,则你的示例将不能按预期工作,可以使用 ./bin 1<>file 0<&1 来模拟它。 - user10678532

0

既然你使用gcc,我假设你有pipe可用。

#include <cstring>
#include <iostream>
#include <unistd.h>

int main() {
    char buffer[1024];
    std::strcpy(buffer, "test");

    int fd[2];
    ::pipe(fd);
    ::dup2(fd[1], STDOUT_FILENO);
    ::close(fd[1]);
    ::dup2(fd[0], STDIN_FILENO);
    ::close(fd[0]);

    ::write(STDOUT_FILENO, buffer, 4);
    while(true) {
        auto const read_bytes = ::read(STDIN_FILENO, buffer, 1024);
        ::write(STDOUT_FILENO, buffer, read_bytes);
#if 0
        std::cerr.write(buffer, read_bytes);
        std::cerr << "\n\tGot " << read_bytes << " bytes" << std::endl;
#endif
        sleep(2);
    }
    return 0;
}

#if 0 部分可以启用调试。我无法直接使用 std::coutstd::cin 使其正常工作,但是了解更多底层流代码的人可能可以微调它。

调试输出:

$ ./io_loop 
test
        Got 4 bytes
test
        Got 4 bytes
test
        Got 4 bytes
test
        Got 4 bytes
^C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接