3

我想编写行为类似于 unix 实用程序的程序。特别是,我想将它们与管道一起使用,例如:

grep foo myfile | ./MyTransformation [--args] | cut -f2 | ...

三个方面让我想知道如何处理 I/O:

  1. 根据Useless Use of Cat Award之类的资源,最好同时支持从标准输入读取和从文件读取(在管道的开头)。这如何最好地完成?我习惯于使用<getopt.h>/<cgetopt>来解析参数。我可以查看除了我的选项之外是否还有另一个文件参数并从中读取。如果没有,请从标准输入读取。这意味着如果提供了 inut 文件,stdin 将被忽略。这是可取的吗?

  2. 根据这个问题,C++ 同步coutcinstdio,因此不能很好地缓冲。这会导致性能大幅下降。一种解决方案是禁用同步:cin.sync_with_stdio(false);. 管道中使用的程序是否应该始终禁用与stdioforcin和的同步cout?还是应该避免使用cincout而是使用他们自己的缓冲 io 形式?

  3. 由于cout将用于程序输出(除非指定了输出文件),因此状态消息(如 % done 之类的详细信息)必须转到其他地方。cerr/stderr似乎是一个显而易见的选择。但是,状态没有错误。

总之,我想知道 c++ 中此类程序的 io ahndling。尽管存在上述问题,是否可以cin使用?cout是否应该以不同的方式处理 I/O?例如,读取和写入缓冲文件,其中 stdin 和 stdout 是默认文件?实施这种行为的推荐方法是什么?

4

2 回答 2

2

如果没有选项,标准习语是:

int returnCode = 0;

void
processFile( std::string const& filename )
{
    if ( filename == "-" ) {
        process( std::cin );
    } else {
        std::ifstream in( filename.c_str() );
        if ( !in.is_open() ) {
            std::cerr << argv[0] << ": cannot open " << filename << std::endl;
            returnCode = 1;
        } else {
            process( in );
        }
    }
}

int
main( int argc, char** argv )
{
    if ( argc == 1 ) {
        processFile( "-" );
    } else {
        for ( int i = 1; i != argc; ++ i ) {
            processFile( argv[i] );
        }
    }
    std::cout.flush()
    return std::cout ? returnCode : 2;
}

然而,有许多变体。我发现自己经常这样做,以至于我编写了一个MultiFileInputStream类,其 (template> 构造函数采用一对迭代器;然后它执行或多或少与上面相同的代码。(所有重要的代码像往常一样,在相应的streambuf。)类似地,我有一个类来解析选项(一旦解析了选项,它看起来就像一个不可变std::vector<std::string>的。所以上面会变成:

int
main( int argc, char** argv )
{
    CommandLine& args = CommandLine::instance();
    args.parse( argc, argv );
    MultiFileInputStream src( args.begin(), args.end() );
    process( src );
    return ProgramStatus::instance().returnCode();
}

(ProgramStatus是另一个有用的类,它处理错误输出和返回码。std::cout当你调用它时刷新和调整错误码returnCode()。)

我确信任何编写 Unix 过滤器程序的人都开发过类似的类。

关于问题 2:sync_with_stdio是 的静态成员std::ios_base,因此您可以在没有对象的情况下调用它: std::ios_base::sync_with_stdio( false );。我发现这不太容易误导,因为调用会影响所有iostream 对象。如果 IO 处理是一个阻塞点,无论如何都要做,但大多数时候,我不打扰。此类程序很少需要任何类型的优化。(请注意,如果你调用了 sync_with_stdio,那么你不应该使用任何 C 风格的 IO。但我看不出有任何理由使用它。)

关于问题 3:错误消息std::cerr总是转到 。您还希望确保返回非零返回码,即使错误不是致命的。就像是:

myprog file1 > tmp && mv tmp file1

都是常见的,如果你有一些问题,并且不生成输出,如果你不返回非零错误代码,那将是一场灾难。(这就是为什么我总是刷新然后检查 . 的状态std::cout。很久很久以前,我的程序的用户做了上述操作,文件非常大,磁盘已满。之后就没有那么满了。从那时起:总是 flush std::cout,并检查它是否有效,然后返回 OK。)

于 2013-09-13T13:16:33.327 回答
-1

您确定要使用 C++ 吗?大多数操作系统比 C++ 更多地依赖 C 和汇编。如果您要编写应用程序,那么 C++ 可能是一个不错的选择,但对于操作系统及其实用程序、shell 和帮助程序,它们通常是用 C 编码的。您可以查看您的 Linux 或 BSD 实现以了解它是如何实现的通过管道、标准输入和标准输出完成。如果您认为 C 适合您,您可以阅读 Kernighan 和 Richie 的 C 书籍“The C programming language”,那里有很多示例如何编写一个使用管道、std i/o 和参数的优秀 C 程序。

于 2013-09-13T10:32:16.940 回答