如何将程序输出重定向为输入
How to redirect program output as its input
我已经编写了一个简单的 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
我期望发生的是输出到标准输出的“文本”,它现在也将成为程序的标准输入,并且它将永远循环。为什么没有发生?
首先,打印到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
是,您最终将标准输入和标准输出都连接到 /dev/tty
(这意味着您可以看到输出)。
但是 TTY 设备永远无法关闭循环,因为它实际上由两个独立的通道组成,一个将输出传递给终端,一个将终端输入传递给进程。
如果您使用常规文件进行输入和输出,则可以关闭循环。如果进程的标准输入连接到它,写入文件的每个字节也将从中读取。只要没有其他进程同时从文件读取,因为流中的每个字节只能读取一次。
因为标准输出和标准输入不创建循环。它们可能指向同一个 tty,但一个 tty 实际上是两个独立的通道,一个用于输入,一个用于输出,并且它们不会相互环回。
您可以尝试通过 运行 您的程序创建一个循环,其 stdin 连接到 pipe 的读取端,并将其 stdout 连接到其写入端。这将适用于 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 而无需阻塞,第一个在 read-only 模式下,第二个在 write-only 模式下 (prog 3<>fifo <fifo >fifo 3>&-
).
对于手头的例子,它们的工作方式都是一样的。在 Linux、:|prog >/dev/fd/0
(和 echo text | strace cat >/dev/fd/0
)上也可以工作——无需使用 mkfifo
.
创建命名管道
由于您使用的是 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::cout
和 std::cin
一起使用,但对 low-level 流代码了解更多的人可能会对此进行调整。
调试输出:
$ ./io_loop
test
Got 4 bytes
test
Got 4 bytes
test
Got 4 bytes
test
Got 4 bytes
^C
我已经编写了一个简单的 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
我期望发生的是输出到标准输出的“文本”,它现在也将成为程序的标准输入,并且它将永远循环。为什么没有发生?
首先,打印到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
是,您最终将标准输入和标准输出都连接到 /dev/tty
(这意味着您可以看到输出)。
但是 TTY 设备永远无法关闭循环,因为它实际上由两个独立的通道组成,一个将输出传递给终端,一个将终端输入传递给进程。
如果您使用常规文件进行输入和输出,则可以关闭循环。如果进程的标准输入连接到它,写入文件的每个字节也将从中读取。只要没有其他进程同时从文件读取,因为流中的每个字节只能读取一次。
因为标准输出和标准输入不创建循环。它们可能指向同一个 tty,但一个 tty 实际上是两个独立的通道,一个用于输入,一个用于输出,并且它们不会相互环回。
您可以尝试通过 运行 您的程序创建一个循环,其 stdin 连接到 pipe 的读取端,并将其 stdout 连接到其写入端。这将适用于 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 而无需阻塞,第一个在 read-only 模式下,第二个在 write-only 模式下 (prog 3<>fifo <fifo >fifo 3>&-
).
对于手头的例子,它们的工作方式都是一样的。在 Linux、:|prog >/dev/fd/0
(和 echo text | strace cat >/dev/fd/0
)上也可以工作——无需使用 mkfifo
.
由于您使用的是 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::cout
和 std::cin
一起使用,但对 low-level 流代码了解更多的人可能会对此进行调整。
调试输出:
$ ./io_loop
test
Got 4 bytes
test
Got 4 bytes
test
Got 4 bytes
test
Got 4 bytes
^C