【问题标题】:how to check program is writing to terminal如何检查程序正在写入终端
【发布时间】:2016-06-05 18:22:17
【问题描述】:

这是我在codereview - Colorful output on terminal 上发布的问题的后续,我试图在终端上输出彩色字符串并通过isatty() 调用检测它。然而正如@Jerry Coffin 指出的那样 -

您使用 isatty 来检查标准输出是否连接到终端,无论您正在写入什么流。这意味着其余函数只有在您将 std::cout 作为它们要写入的流传递时才能正常工作。否则,您可能会在写入非 TTY 内容时允许格式化,而您可能会在写入 TTY 内容时禁止格式化。

这是我不知道的事情(读作没有经验),我什至不知道 cin/cout 可以重定向到其他地方。所以我试图阅读更多关于它的内容,并发现了一些关于 SO 的现有问题。这是我一起破解的:

// initialize them at start of program - mandatory

std::streambuf const *coutbuf = std::cout.rdbuf();
std::streambuf const *cerrbuf = std::cerr.rdbuf();
std::streambuf const *clogbuf = std::clog.rdbuf();


// ignore this, just checks for TERM env var

inline bool supportsColor()
    {
        if(const char *env_p = std::getenv("TERM")) {
            const char *const term[8] = {
                "xterm", "xterm-256", "xterm-256color", "vt100",
                "color", "ansi",      "cygwin",         "linux"};
            for(unsigned int i = 0; i < 8; ++i) {
                if(std::strcmp(env_p, term[i]) == 0) return true;
            }
        }
        return false;
    }

rightTerm = supportsColor();

// would make necessary checks to ensure in terminal

inline bool isTerminal(const std::streambuf *osbuf)
    {
        FILE *currentStream = nullptr;
        if(osbuf == coutbuf) {
            currentStream = stdout;
        }
        else if(osbuf == cerrbuf || osbuf == clogbuf) {
            currentStream = stderr;
        }
        else {
            return false;
        }
        return isatty(fileno(currentStream));
    }

// this would print checking rightTerm && isTerminal calls

inline std::ostream &operator<<(std::ostream &os, rang::style v)
    {
        std::streambuf const *osbuf = os.rdbuf();

        return rightTerm && isTerminal(osbuf)
                   ? os << "\e[" << static_cast<int>(v) << "m"
                   : os;
    }

我的主要问题是,虽然我已经手动测试过,但我不知道这可能会失败的情况或它可能包含的错误。这是做这件事的正确方法吗?有什么我可能遗漏的吗?


这是一个运行的最小示例(您还需要一个带有随机数据的in.txt):

#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <cstring>

void f();
bool supportsColor();

// sample enum for foreground colors
enum class fg : unsigned char {
    def     = 39,
    black   = 30,
    red     = 31,
    green   = 32,
    yellow  = 33,
    blue    = 34,
    magenta = 35,
    cyan    = 36,
    gray    = 37
};

// initialize them at start of program - mandatory
// so that even if user redirects, we've a copy
std::streambuf const *coutbuf = std::cout.rdbuf();
std::streambuf const *cerrbuf = std::cerr.rdbuf();
std::streambuf const *clogbuf = std::clog.rdbuf();

// check if TERM supports color
bool rightTerm = supportsColor();

// Here is the implementation of isTerminal
// which checks if program is writing to Terminal or not
bool isTerminal(const std::streambuf *osbuf)
{
    FILE *currentStream = nullptr;
    if(osbuf == coutbuf) {
        currentStream = stdout;
    }
    else if(osbuf == cerrbuf || osbuf == clogbuf) {
        currentStream = stderr;
    }
    else {
        return false;
    }
    return isatty(fileno(currentStream));
}

// will check if TERM supports color and isTerminal()
inline std::ostream &operator<<(std::ostream &os, fg v)
{
    std::streambuf const *osbuf = os.rdbuf();

    return rightTerm && isTerminal(osbuf)
               ? os << "\e[" << static_cast<int>(v) << "m"
               : os;
}


int main()
{

    std::cout << fg::red << "ERROR HERE! " << std::endl
              << fg::blue << "ERROR INVERSE?" << std::endl;

    std::ifstream in("in.txt");
    std::streambuf *Orig_cinbuf = std::cin.rdbuf(); // save old buf
    std::cin.rdbuf(in.rdbuf()); // redirect std::cin to in.txt!

    std::ofstream out("out.txt");
    std::streambuf *Orig_coutbuf = std::cout.rdbuf(); // save old buf
    std::cout.rdbuf(out.rdbuf()); // redirect std::cout to out.txt!

    std::string word;
    std::cin >> word;                      // input from the file in.txt
    std::cout << fg::blue << word << "  "; // output to the file out.txt

    f(); // call function

    std::cin.rdbuf(Orig_cinbuf);   // reset to standard input again
    std::cout.rdbuf(Orig_coutbuf); // reset to standard output again

    std::cin >> word;  // input from the standard input
    std::cout << word; // output to the standard input
    return 0;
}

void f()
{
    std::string line;
    while(std::getline(std::cin, line)) // input from the file in.txt
    {
        std::cout << fg::green << line << "\n"; // output to the file out.txt
    }
}

bool supportsColor()
{
    if(const char *env_p = std::getenv("TERM")) {
        const char *const term[8] = {"xterm",  "xterm-256", "xterm-256color",
                                     "vt100",  "color",     "ansi",
                                     "cygwin", "linux"};
        for(unsigned int i = 0; i < 8; ++i) {
            if(std::strcmp(env_p, term[i]) == 0) return true;
        }
    }
    return false;
}

我还标记了c 语言,虽然这是c++ 代码,因为相关代码是黑白共享的,我不想错过任何建议

【问题讨论】:

  • 请尽量减少代码,只有相关部分应该这样做
  • 您可以使用 test | lesstest &gt; test.txt 之类的东西进行测试。
  • @tadman ^ 回答“问题”
  • @tadman 直到我在此页面中看到 function included in multiple source files must be inline - en.cppreference.com/w/cpp/language/inline 之前,我并没有经常使用内联。
  • @tadman, inline 只是与性能间接相关,这意味着函数体可以出现在多个翻译单元中(即在标题中)而不会出现多个定义错误。

标签: c++ linux c++11 terminal


【解决方案1】:

OP 的问题:

我的主要问题是,虽然我已经手动测试过,但我不知道这可能会失败的情况或它可能包含的错误。这是做这件事的正确方法吗?有什么我可能会丢失的吗?

并非所有终端都支持所有功能;此外,TERM 变量最常用于选择特定的终端描述

通常的方法是使用终端数据库而不是硬编码。这样做,你的方法

inline bool supportsColor()

inline std::ostream &operator<<(std::ostream &os, rang::style v)

将检查终端功能,例如,使用tigetnum(用于颜色数量)、tigetstr(用于终端应该支持的实际转义序列)。您可以像 isatty 函数一样轻松地包装它们。

进一步阅读:

【讨论】:

    【解决方案2】:

    要在 POSIX 上检查标准输出是终端,只需使用 isatty(3)

     if (isatty(STDOUT_FILENO)) {
       /// handle the stdout is terminal case
     }
    

    你也可以使用/dev/tty,见tty(4);例如如果您的程序 myprog 在像 ./myprog some arguments | less 这样的命令管道中启动,您仍然可以 fopen("/dev/tty","w") 输出到控制终端(即使 stdout 是一个管道)。

    有时,程序在没有任何控制终端的情况下运行,例如通过crontab(5)at(1)

    【讨论】:

    • 我已经在使用isatty(STDOUT_FILENO),因为isatty(fileno(stdout)) 是同样的东西。
    猜你喜欢
    • 2012-01-23
    • 2015-04-13
    • 1970-01-01
    • 2019-07-18
    • 1970-01-01
    • 2018-04-12
    • 2017-05-26
    • 2010-11-27
    • 2012-01-10
    相关资源
    最近更新 更多