4

我的程序是一个我试图用 C++ 编写的通用 shell。除了从命令行获取命令外,它还必须能够读取文件中的命令——文件名作为可选参数传递,而不是通过重定向传递。

如果 arg 存在,我打开传递的文件名,否则我打开“/dev/stdin”。我对打开开发文件并不感到兴奋,这不是我的主要问题,但如果有人有更好的方法,我很想听听。

最终我必须读取给 shell 的命令,但首先我必须显示提示,如果我从标准输入读取,或者如果输入来自文件,则跳过提示。getCommand我的问题是:有没有比声明全局或传递布尔或类似黑客更好的方法来确定输入流是否为标准输入?

我突然想到,如果我能以某种方式使用 std::cin 而不是打开 /dev 文件,我可以将流作为istream. 这样会更容易区分两者吗?例如if (source == cin)

感谢您的任何建议。

bool getCommand(ifstream source, std::string command)
{
    if (source == stdin)
        //print prompt to stdout

    // do the rest of stuff

    return true;
}


int main(int argc, char *argv[])
{
    std::ifstream input;
    std::string command;

    if (argc == 2)
    {
        input.open(argv[1], std::ifstream::in);

        if (! input)
        {
            perror("input command file stream open");
            exit(EXIT_FAILURE);
        }
    }
    else
    {
        input.open("/dev/stdin", std::ifstream::in);

        if (! input)
        {
            perror("input stdin stream open");
            exit(EXIT_FAILURE);
        }
    }

    //.......

    if (getCommand(input, command))
        //.......
}
4

2 回答 2

3

如果您std::istream&用作变量的类型,那么您可以使用std::cin而不是打开/dev/stdin

std::ifstream fileinput;
if (argc == 2)
{
    fileinput.open(argv[1], std::ifstream::in);

    if (! fileinput)
    {
        perror("input command file stream open");
        exit(EXIT_FAILURE);
    }
}
std::istream &input = (argc == 2) ? fileinput : std::cin;

您的问题的其余部分应该是关于识别 tty(检测 stdin 是终端还是管道?)。但是如果做不到这一点,一旦您进行了上述更改,您就可以与以下地址进行比较std::cin

if (&input == &std::cin) {
    std::cout << "prompt:";
}

显然,这不会识别命令行参数是 的情况/dev/stdin,所以这有点麻烦。但我可能会争辩说,考虑到有趣的要求,这实际上是一种改进,因为有人至少可以写yourprogram /dev/stdin < input.txt,或者sort input.txt | yourprogram /dev/stdin为了解决你的限制并避免提示:-)

于 2013-10-04T00:08:36.997 回答
2

首先,如果您想std::cin从 a 或从 a阅读std::ifstream,我会根据 a 来实现工作,std::istream当然避免打开/dev/stdin

std::istream  in(std::cin.rdbuf());
std::ifstream fin;
if (argc == 2) {
    fin.open(av[1]);
    // check that the file is actually open or produce an error
    in.rdbuf(fin.rdbuf());
}
else {
    // determine if you need to create a prompt
}

if (getCommand(in, command)) {
    ...
}

现在,要确定您是否真的需要编写提示,仅确定您是否从标准输入读取是不够的,但您还需要确定标准输入是否连接到带有屏幕和键盘的东西。但是,没有便携的方法可以做到这一点。在 UNIX 上,有类似的函数isatty()可用于确定文件描述符 0 是否连接到 tty。我会使用这些信息来设置一个iword()然后可以用来检查流是否需要提示:

static int const needs_prompt = std::ios_base::xalloc();
...
in.iword(needs_prompt) = isatty(0);
...
if (in.iword(needs_prompt)) {
    std::cout << "pompt>";
}

如果你想变得花哨,你可以创建一个自定义std::streambuf,它与一个连接到输入流流std::ostreamtie()ed 并在sync(): 每次对输入流的读取操作完成时刷新tie()ed 。std::ostream但是,这要求您只为生成的每个提示阅读一次,例如使用std::getline(in, line). 显然,如果您不需要提示,则tie()不需要std::ostream. 下面是一个演示自动提示方法的简单程序(不做任何其他业务):

#include <iostream>
#include <streambuf>

struct prompt
    : std::streambuf
{
    std::string   d_prompt;
    std::ostream& d_out;
    prompt(std::string const& p, std::ostream& out)
        : d_prompt(p)
        , d_out(out)
    {
    }
    int sync() {
        this->d_out << this->d_prompt << std::flush;
        return 0;
    }
};

int main()
{
    prompt       p("prompt>", std::cout);
    std::ostream helper(&p);
    std::cin.tie(&helper);

    for (std::string line; std::getline(std::cin, line); ) {
        std::cout << "read '" << line << "'\n";
    }
}
于 2013-10-04T00:08:47.637 回答