【问题标题】:How to implement grep in C++ so it works with pipes (stdin,etc.)?如何在 C++ 中实现 grep 以便它与管道(stdin 等)一起使用?
【发布时间】:2011-04-20 13:24:03
【问题描述】:

我想为我在 Windows 中执行的 Shell 实现 grep(仅用于学习目的)。
我知道 grep 的语法如下:

grep pattern files

所以我可以制作如下函数:

int grep(string stringToMatch, string fileName) // just one file
{
   // search file for stringToMatch
   // print line containing stringToMatch
}

我的困惑是,当我使用这样的管道时,grep 应该如何工作:

ls | grep someword

我实现了“ls”,将所有输出放入向量中并返回,所以我想我的 grep 应该在向量中搜索结果。那么正确的 grep 函数应该是什么样子呢?我需要 2 个 grep 函数吗?

提前致谢。

【问题讨论】:

  • 为什么要用 C++ 实现 UNIX Shell?
  • 我不确定你想要实现多少,但你当然可以使用正则表达式在你的 grep 函数中实现字符串匹配部分。正则表达式参考msdn.microsoft.com/en-us/library/zcwwszd7(v=vs.80).aspx
  • @rubenvb:我正在使用 boost 来熟悉它,所以我想构建一个简单的 shell,使用 boost 库。 @llho:我想我可以使用 boost.regex,谢谢。
  • 啊,好吧,学术目的ftw!我会选择Boost.Regex 来完成模式匹配。

标签: c++ pipe


【解决方案1】:

您想查看传递了多少命令行参数。如果只有一个,那么您假设您使用的是标准输入而不是文件。

在 C++ 中,这可以通过在函数中使用对 std::istream 的引用来抽象。在调用函数之前,您决定(并创建)一个std::ifstream(如果合适),否则使用std::cin

这样你的函数就变成了:

int grep(string stringToMatch, std::istream& in) // just one file
{
   // search file for stringToMatch
   // print line containing stringToMatch
}

您可以使用条件(在main 中使用argcargv)来执行以下任一操作:

grep(string, std::cin);

std::ifstream file(fileName.c_str());
grep(string, file);

【讨论】:

    【解决方案2】:

    阅读UNIX filtershere

    Unix 过滤器通过标准输入和标准输出进行通信。 IE。第一个进程的标准输出在第二个进程的标准输入上接收。

    标准输入和输出本质上是二进制/文本流

    此方法可以链接。外壳通常是管理 - 环境 - 进程的启动、监控和退出 - 互连

    所以 0. 用户给出命令,例如ls 1. shell 找到命令,创建一个新进程,从终端连接标准输入和从终端连接标准输出, 2.等待程序执行 3. 用子进程的结果设置环境

    如果你说你在向量中有 'ls' 输出,恐怕你并没有真正接近以真正的方式编写 shell

    如果你想做一个没有进程管理、管道、重定向等特性的 shell,最有用的工具是 std::istream 和 std::ostream(或 Boost IOStreams 库)。

    一个非常简单(非常愚蠢)的 grep 版本可能如下所示:

    #include <iostream>
    #include <string>
    
    static bool is_match(const std::string& text, const std::string& pattern)
    {
        // FIXME TODO use actual (posix?) regex, boost regex, regex++ or whatnot
        return std::string::npos != text.find(pattern);
    }
    
    
    int main(int argc, const char* argv[])
    {
        switch(argc)
        {
            case 0: case 1:
                std::cerr << "specify the pattern" << std::endl;
                return 254;
            case 2:
                break;
            default:
                std::cerr << "not implemented" << std::endl;
                return 255;
        }
        const std::string pattern(argv[1]);
    
        std::string line;
        while (std::getline(std::cin, line))
        {
            if (is_match(line, argv[1]))
                std::cout << line << std::endl;
        }
    
        return 0;
    }
    

    有更好的例子e.g. here,但从问题来看,我认为这是下一步提供信息的步骤;)

    另请注意,Boost IOstreams 库似乎包含 built-in support for pipelines

    【讨论】:

    • 感谢您的帖子非常有用,是的,我对 grep 的想法与您的代码相同。但是我在 windows 中编码,所以我不能使用 unix 过滤器。
    • 有一点点区别:控制台应用程序在 Windows 上具有标准输入、标准输出和标准错误。您会惊讶于 posix complete Win32 是开箱即用的。所以,是的,你可以拥有“UNIX 过滤器”(无论是什么),它们就完成了:Cygwin、UnxUtils、MingW 等都在 Windows 上提供了完整的 shell 环境。
    • 最重要的区别是 CMD.EXE 通常不做“真正的管道”或“真正的重定向”(它使用临时文件而不是命名管道)。只是不要使用 CMD.EXE :)
    • 嗯,用VS2010,我猜我用的是cmd.exe :)
    • @sehe:MS-DOS command.com 确实如此,但 cmd.exe 从未如此。 cmd.exe 从一开始就使用“真正的”管道(不仅回到 Windows NT 3.1,而且一直回到 OS/2 1.0,到此为止)。
    【解决方案3】:

    您的 grep 函数应该在 FILE *(或 C++ 等效项)上工作。如果您获得作为参数传递的文件名,请打开该文件。如果没有,请阅读stdin

    【讨论】:

    • 在新的 C++ 代码中使用 FILE * 和更普遍的 stdio.h 通常被认为是不好的做法,除非有其他令人信服的理由,但这里似乎并非如此。
    • @awoodland,“通常被认为是不好的做法”是谁?你?当没有什么理由时,你应该尽量减少口头评判。
    • 对于我的评论和comp.lang.c++ FAQ,这通常被认为是相当权威的。
    • 抱歉,这并不是要在口头上做出判断,但我确实认为向新程序员建议“最佳实践”而不是遗留代码是一个强有力的论据。在我意外鼓励的不良做法列表中,数组 vs std::vector、C++ 样式转换和 std::string vs const char * 都在上面。
    【解决方案4】:

    当命令以您发布的这种语法出现时,它应该从标准输入中读取其输入。因此,您需要传递给函数的不是文件名字符串,而是文件的打开文件描述符或 FILE*,无论是文件系统驻留文件还是标准输入。

    类似:

    FILE *f;
    
    if (argc == 3)
      f = fopen(argv[2], "r");
    else
      f = stdin;
    
    grep(argv[1], f);
    

    请注意,如果您执行“ls | grep bar foo”,grep 将忽略 ls 的输出并匹配文件“foo”中的“bar”。因此,上面的代码反映了(嵌入了很多缺陷,并且不完全,因为 grep 可以匹配多个文件)grep 的行为。

    【讨论】:

    • 在新的 C++ 代码中使用 FILE * 和更普遍的 stdio.h 通常被认为是不好的做法,除非有令人信服的理由,否则这里似乎并非如此。
    • @awoodland 我是一名 C 程序员,对此感到抱歉 =D 虽然我看到了标签,但我想公开我的想法。
    【解决方案5】:

    您可能想要使用像 Boost Regex 这样的库并计算在您的 shell 中输入的模式的结果。

    对于管道,这是 shell 的一个特性,而不是 grep。您可以签出Boost InterprocessBoost Asio 库来实现它。 Boost Asio 支持多种 POSIX 进程间通信机制。

    【讨论】:

    • 我对这个问题的解读是,OP 在问“一个函数如何使用文件或标准输入,但不需要知道哪个?”
    • @awoodland:题主问的是如何在c++中实现grep,所以我建议了几个参考。
    • @awoodland:是的,你是对的,但使用 boost.regex 的解决方案也可能有效:)
    • 感谢您的建议,我也考虑过 boost.regex
    【解决方案6】:

    当用户设置第二个参数时重新打开stdin

    int grep(string stringToMatch, FILE *fp_in);
    
    
    int main(int argc, char **argv)
    {
        if (argc == 2) {
            freopen(argv[2], "r", stdin);
        }
        ...
    
    }
    

    【讨论】:

    • 为什么要在传递 FILE * 时重新打开标准输入?为什么在现代 C++ 中使用 FILE *?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2020-05-26
    相关资源
    最近更新 更多