【问题标题】:How to redirect program output as its input如何将程序输出重定向为其输入
【发布时间】:2021-01-16 16:14:55
【问题描述】:

我为教程目的编写了一个简单的 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&gt;&amp;1 我期望发生的是输出到标准输出的“文本”,现在它也将成为程序的标准输入,它将永远循环。为什么没有发生?

【问题讨论】:

  • I/O 流不是这样工作的。写入终端不会将数据放入键盘输入中。
  • 你可以使用std::stringstream来做这样的事情。
  • 究竟为什么要这样做?听起来很有趣,我喜欢这个问题,但我很好奇是否有一个实际的用例。
  • @Barmar 你能帮我区分一下吗?不是通过重定向0>&1来完成的吗?
  • 但是stdin是输入流,不能用&gt;重定向,应该用&lt;。但这是同样的问题。

标签: c++ linux shell stdout stdin


【解决方案1】:

首先,打印到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 设备永远无法关闭循环,因为它实际上由两个独立的通道组成,一个将输出传递给终端,一个将终端输入传递给进程。

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

【讨论】:

  • 我修正了为什么必须输出 std::endl 的解释(因此 getline() 看到终止的行进入;与行缓冲无关)
  • 删除了我的第一条评论,改为放在答案中,以便更全面地回答问题。
  • 修正了关于 TTY 设备特殊行为的解释(感谢@user414777)
  • 感谢您的努力。您的示例非常准确,这是我完全理解问题的关键信息:“但是 TTY 设备永远无法关闭循环,因为它实际上由两个独立的通道组成,一个将输出传递到终端,一个传递进程的终端输入"
【解决方案2】:

由于您使用的是 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

【讨论】:

    【解决方案3】:

    因为标准输出和标准输入不创建循环。它们可能指向同一个 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
    ...
    

    注意:使用&lt;&gt;fifo 打开命名管道 (fifo) 的方式是为了同时打开其读取端和写入端,从而避免阻塞。可以简单地从标准输入 (prog &lt;&gt;fifo &gt;&amp;0) 复制标准输出,而不是从其路径重新打开先进先出,或者先将先进先出作为不同的文件描述符打开,然后可以打开标准输入和标准输出而不会阻塞,第一个处于只读模式,第二个处于只写模式 (prog 3&lt;&gt;fifo &lt;fifo &gt;fifo 3&gt;&amp;-)。

    它们都将与手头的示例相同。在 Linux 上,:|prog &gt;/dev/fd/0(和echo text | strace cat &gt;/dev/fd/0)也可以工作——无需使用mkfifo 创建命名管道。

    【讨论】:

    • 您实际上不需要 FIFO 来关闭循环。这只会使画面复杂化。一个简单的文件就足够了。我在我的回答中证明了这一点。你是对的,TTY 永远无法关闭循环。我应该在这方面更正我的答案。
    • 恕我直言,它使用的常规文件会使图片复杂化(除了填满磁盘;-))。它涉及文件指针位置和对同一文件的非同步读写等棘手问题。
    • 是的,但是 FIFO 在输入和输出之间添加了一个中间实例。输入和输出不是直接连接的。这是 OP 想要实现的。对于常规文件,您将文件内容视为一种不必要的副作用。该问题询问如何将过程输出直接反馈到其输入;并且常规文件尽可能接近(或我能想到的)。
    • @kisch 你很困惑——因为你打开文件两次,你实际上是在使用 两个 引用同一个磁盘索引节点的文件,并且 这是一个侥幸,读取和写入按预期排序。 (而且你在两个打开的文件对象之间也有十几层,但这不是重点)。当然,如果您使用单个文件,以读写模式打开,使用相同的文件指针,您的示例将无法按预期工作,您可以使用 ./bin 1&lt;&gt;file 0&lt;&amp;1 进行模拟。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    相关资源
    最近更新 更多