【问题标题】:Sending data to a program via stdin and ostream. (C++)通过 stdin 和 ostream 向程序发送数据。 (C++)
【发布时间】:2015-11-14 22:36:40
【问题描述】:

我想将数据从我的 C++ 程序中发送到外部管道,如下所示:

FILE* file = popen("my_prog -opt | other_prog", "w");
std::ostream fileStream = some_function(file);
fileStream << "some data";

我知道没有简单的跨平台方法来做第二行,但是除了popen 之外,还有什么方法可以完成同样的事情吗?我不需要使用popen,但我确实需要使用ostream。它至少需要使用clanggcc 进行编译,但最好可以使用任何编译器。我也可以更改处理管道的方式,但我没有 my_progother_prog 的来源。

【问题讨论】:

  • 试试看boost::iostreams::file_descriptor。它有非常糟糕的文档,但它可以帮助你,而且它是跨平台的。
  • 您有什么具体原因需要使用ostream
  • 您能否在创建流程之前将所有输入输入fileStream,然后将其作为输入提供给my_prog,或者在创建流程之后才知道某些输入?跨度>
  • @James_Parsons 我的代码库中有很多其他代码可以与ostreams 一起使用。
  • 您是否考虑过创建自己的实现std::ostream 的类?我不确定复杂性,但我猜想,简单的不太优化的版本可以在一天内完成。我确信你不应该只在代码中写popen:在构造函数中使用带有popen 的RAII 类,在析构函数中使用pclose

标签: c++ popen ostream filehandle


【解决方案1】:

使用FILE* 作为底层目标创建流缓冲区并使用这样的流缓冲区创建相应的std::ostream 是直接的。它大致看起来像这样:

#include <stdio.h>
#include <streambuf>
#include <ostream>

class stdiobuf
    : public std::streambuf {
    enum { bufsize = 2048 };
    char buffer[bufsize];
    FILE* fp;
    int   (*close)(FILE*);
    int overflow(int c) {
        if (c != std::char_traits<char>::eof()) {
            *this->pptr() = std::char_traits<char>::to_char_type(c);
            this->pbump(1);
        }
        return this->sync()
            ? std::char_traits<char>::eof()
            : std::char_traits<char>::not_eof(c);
    }
    int sync() {
        std::streamsize size(this->pptr() - this->pbase());
        std::streamsize done(this->fp? fwrite(this->pbase(), 1, size, this->fp): 0);
        this->setp(this->pbase(), this->epptr());
        return size == done? 0: -1;
    }
public:
    stdiobuf(FILE* fp, int(*close)(FILE*) = fclose)
        : fp(fp)
        , close(close) {
        this->setp(this->buffer, this->buffer + (this->fp? bufsize - 1: 0));
    }
    ~stdiobuf() {
        this->sync();
        this->fp && this->close(this->fp);
    }
};
class opipestream
    : private virtual stdiobuf
    , public std::ostream {
public:
    opipestream(std::string const& pipe)
        : stdiobuf(popen(pipe.c_str(), "w"), pclose)
        , std::ios(static_cast<std::streambuf*>(this))
        , std::ostream(static_cast<std::streambuf*>(this)) {
    }
};

int main()
{
    opipestream out("/usr/bin/sed -e 's/^/xxxx /'");
    out << "Hello\n";
    out << "world\n";
}

基本思想是您可以通过实现流缓冲区来创建新流。上面的实现应该是相当完整的。可以改进不完整缓冲区时的错误处理,尽管最可能的错误情况是管道已关闭并且实际上没有太多可以做的事情。

【讨论】:

  • 谢谢,这正是我所需要的。
【解决方案2】:

由于缺少std::filebuf ctor,我的旧答案仅在 Windows 下有效。正如user4815162342 指出的那样,替代方案可能是使用__gnu_cxx::stdio_filebuf&lt;char&gt;

我修补了一些现在应该可以在 windows 和 linux 上运行的东西,但是你的平台可能无法全部运行。

#ifdef __GNUC__
    #include <ext/stdio_sync_filebuf.h>    
    typedef __gnu_cxx::stdio_sync_filebuf<char> popen_filebuf;
#elif _MSC_VER
    #include<fstream>
    typedef std::filebuf popen_filebuf;
    FILE*(*popen)(const char*, const char*) = _popen;
#else
    static_assert(false, "popen_filebuf is not available for this platform");
#endif

int main()
{
    FILE* file = popen("my_prog -opt | other_prog", "w");

    popen_filebuf buffer(file);
    std::ostream fileStream(&buffer);

    fileStream << "some data";

    return 0;
}

【讨论】:

  • 该标准并未强制要求 std::filebufFILE* 的形式实现,尽管多个实现方式确实(或确实)这样做了。
  • 如我的答案编辑所示,Linux 上的 g++ 也支持__gnu_cxx::stdio_sync_filebuf,因此您可以简单地将popen_filebuf 键入@ 到__gnu_cxx::stdio_sync_filebuf&lt;char&gt;
  • @user4815162342 这导致error: no matching function for call to '__gnu_cxx::stdio_filebuf&lt;char&gt;::stdio_filebuf(FILE*&amp;)'
  • stdio_filebufstdio_sync_filebuf是不同的类,需要使用后者(如注释所示)直接从FILE *构造。
  • @user4815162342 调整了答案。感谢您的提示。现在容易多了。
【解决方案3】:

如果您事先知道自己支持哪些平台,则可以使用特定于平台的扩展从文件描述符中创建 iostream。例如,如图所示here,GNU libstdc++ 提供了__gnu_cxx::stdio_sync_filebuf,可用于从FILE * 创建filebuf。从std::iostream 继承并使用适当的filebuf 对其进行初始化的类,在Linux 上使用g++ 5.2 和clang++ 3.7 进行了测试,如下所示:

#include <iostream>
#include <ext/stdio_sync_filebuf.h>

class StdioStream:
  private __gnu_cxx::stdio_sync_filebuf<char>, public std::iostream {
public:
  explicit StdioStream(FILE *fp):
    __gnu_cxx::stdio_sync_filebuf<char>(fp), std::iostream(this) { }
};

int main()
{
    FILE* file = popen("my_prog -opt | other_prog", "w");
    StdioStream fileStream(file);
    fileStream << "some data";
}

请注意,不能只定义一个返回std::ostreamsome_function,因为后者有一个私有复制构造函数,并且因为中间filebuf 需要与流一起被破坏。上面的示例使用多重继承,以便能够在 iostream 基类之前初始化 stdio_sync_filebuf(否则将是一个成员) - 有关详细信息,请参阅 base-from-member

引用的answer 还包含 MSVC++ iostream 的等效代码。

【讨论】:

  • 你为什么不从std::iostream派生?
  • @Walter 好点,我现在更新了从iostream 继承的答案。 member-to-base 问题需要多重继承,这会降低可读性,但这为StdioStream 带来了使用的简单性,而不是证明权衡是合理的。
  • @user4815162342 为什么不合并我们的答案:rextester.com/PFJNUL66673goo.gl/NzKDeh
  • @SimonKraemer 您已经做到了,通过逐步编辑您的原始答案以采用我的方法。现在,Dietmar 提供了他的便携式答案的测试版本,并且他的答案被接受了,这点无论如何都没有实际意义。
  • @user4815162342 啊 - 我没有注意到 Dietmar 的新答案。 ;-)
【解决方案4】:

我对原始问题的评论的扩展。

您可以将所有输入保存到磁盘上的文件中,然后将该文件作为输入提供给my_prog

ofstream fileStream("input.txt");
fileStream << "some data";
// insert other input
fileStream.close();
FILE *file = popen("my_prog -opt <input.txt | other_prog", "w");

你可以在这里使用除了 popen 之外的其他东西,可能是直接的system 调用。全部完成后,您需要删除 input.txt 文件。

【讨论】:

  • 虽然不可移植,但我也看到了使用原生 API 创建进程并使用替代句柄代替标准输入、标准输出等的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-25
  • 1970-01-01
  • 2021-04-15
相关资源
最近更新 更多