将stdin的交互式输入与异步输出结合到stdout

时间:2015-05-24 03:51:43

标签: c++ c linux unix console

我的测试应用程序将日志写入stderr并使用stdin接收来自用户的交互式命令。不用说,任何stderr输出都会破坏终端中的用户输入(和命令提示符)。例如,此命令行(_是光标位置):

Command: reboo_

将成为:

Command: reboo04-23 20:26:12.799 52422  2563 D run@main.cpp:27 started
_
log()致电后

为了解决这个问题,我想在终端中使用旧的Quake控制台,日志在当前输入行的上方一行。换句话说,我想改为:

04-23 20:26:12.799 52422  2563 D run@main.cpp:27 started
Command: reboo_

我可以修改记录代码和读取用户输入的代码。希望它适用于Linux和OS X. log()函数可以从不同的线程调用。 log()函数是stderr的唯一作者。

欢迎修复该问题(损坏的输入行)的其他建议。我正在寻找一种可以在没有额外库(如Curses)的情况下实现的解决方案。我试图谷歌,但意识到我需要一种惯用的启动才能理解我想要的东西。

Upate

感谢 Jonathan Leffler 评论,我意识到我还应该提到分离 stderrstdout并不重要。由于我控制log()函数,因此将其写入stdout而不是stderr并不是问题。但是,不确定它是否使任务更容易。

更新

制作出似乎足够好的东西:

void set_echoctl(const int fd, const int enable)
{
    struct termios tc; 
    tcgetattr(fd, &tc);
    tc.c_lflag &= ~ECHOCTL;
    if (enable)
    {   
        tc.c_lflag |= ECHOCTL;
    }   
    tcsetattr(fd, TCSANOW, &tc);
}

void log(const char *const msg)
{
        // Go to line start
        write(1, "\r", 1);
        // Erases from the current cursor position to the end of the current line
        write(1, "\033[K", strlen("\033[K"));

        fprintf(stderr, "%s\n", msg);

        // Move cursor one line up
        write(1, "\033[1A", strlen("\033[1A"));
        // Disable echo control characters
        set_echoctl(1, 0);
        // Ask to reprint input buffer
        termios tc;
        tcgetattr(1, &tc);
        ioctl(1, TIOCSTI, &tc.c_cc[VREPRINT]);
        // Enable echo control characters back
        set_echoctl(1, 1);
}

但是,它不支持命令提示符(输入行开头的“Command:”)。但可能我可以有两行 - 一个用于命令提示符,另一个用于输入本身,如:

Command: 
reboo_

3 个答案:

答案 0 :(得分:2)

这是我做的事情。打开3个控制台:

控制台#1:(运行程序,输入std :: cin)

> ./program > output.txt 2> errors.txt

控制台#2:(查看std :: cout)

> tail -f output.txt

控制台#3:(查看std :: cerr)

> tail -f errors.txt

任何程序输入都输入控制台:#1

您可以使用Terminator这样的控制台,让您将屏幕拆分为不同的部分:

enter image description here

答案 1 :(得分:2)

以下是我提出的最终解决方案。它实际上是一个工作示例,它产生N个线程并从每个线程发出日志。同时允许交互式用户输入命令。唯一支持的命令是"退出"但是。其他命令会被默默忽略。它有两个小的(在我的情况下)缺陷。

首先一个是命令提示符必须在一个单独的行上。像那样:

Command:
reboo_

原因是VREPRINT控制字符也会发出新行。所以我没有找到如何在没有新行的情况下重新打印当前输入缓冲区的方法。

第二次是在打印日志行的同时输入符号时偶尔出现的闪烁。但是尽管闪烁,但最终结果是一致的,并且没有观察到线重叠。也许我会弄清楚如何避免它变得光滑和干净,但它已经足够好了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/termios.h>
#include <sys/ioctl.h>

static const char *const c_prompt = "Command: ";
static pthread_mutex_t g_stgout_lock = PTHREAD_MUTEX_INITIALIZER;

void log(const char *const msg)
{
    pthread_mutex_lock(&g_stgout_lock);
    // \033[1A - move cursor one line up
    // \r      - move cursor to the start of the line
    // \033[K  - erase from cursor to the end of the line
    const char preface[] = "\033[1A\r\033[K";
    write(STDOUT_FILENO, preface, sizeof(preface) - 1);

    fprintf(stderr, "%s\n", msg);
    fflush(stdout);

    const char epilogue[] = "\033[K";
    write(STDOUT_FILENO, epilogue, sizeof(epilogue) - 1);

    fprintf(stdout, "%s", c_prompt);
    fflush(stdout);

    struct termios tc;
    tcgetattr(STDOUT_FILENO, &tc);
    const tcflag_t lflag = tc.c_lflag;
    // disable echo of control characters
    tc.c_lflag &= ~ECHOCTL;
    tcsetattr(STDOUT_FILENO, TCSANOW, &tc);
    // reprint input buffer
    ioctl(STDOUT_FILENO, TIOCSTI, &tc.c_cc[VREPRINT]);
    tc.c_lflag = lflag;
    tcsetattr(STDOUT_FILENO, TCSANOW, &tc);

    pthread_mutex_unlock(&g_stgout_lock);
}

void *thread_proc(void *const arg)
{
    const size_t i = (size_t)arg;
    char ts[16];
    char msg[64];
    for (;;)
    {
        const useconds_t delay = (1.0 + rand() / (double)RAND_MAX) * 1000000;
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
        usleep(delay);
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0);
        time_t t;
        time(&t);
        ts[strftime(ts, sizeof(ts), "%T", localtime(&t))] = 0;
        snprintf(msg, sizeof(msg), "%s - message from #%zu after %lluns",
                 ts, i, (unsigned long long)delay);
        log(msg);
    }
}


int main()
{
    const size_t N = 4;
    pthread_t threads[N];
    for (size_t i = N; 0 < i--;)
    {
        pthread_create(threads + i, 0, thread_proc, (void *)i);
    }
    char *line;
    size_t line_len;
    for (;;)
    {
        pthread_mutex_lock(&g_stgout_lock);
        fprintf(stdout, "%s\n", c_prompt);
        fflush(stdout);
        pthread_mutex_unlock(&g_stgout_lock);
        line = fgetln(stdin, &line_len);
        if (0 == line)
        {
            break;
        }
        if (0 == line_len)
        {
            continue;
        }
        line[line_len - 1] = 0;
        line[strcspn(line, "\n\r")] = 0;
        if (0 == strcmp("exit", line))
        {
            break;
        }
    }
    for (size_t i = N; 0 < i--;)
    {
        pthread_cancel(threads[i]);
        pthread_join(threads[i], 0);
    }
    return 0;
}

使用的相关文档的链接:

答案 2 :(得分:0)

在更新问题后,您可能需要查看readline库:

它将用户输入的底线分区,并将所有内容输出到其上方的行。它还提供可配置的提示,甚至还具有记录输入的输入历史记录的功能。

以下是一个示例,您可以从log()函数中获取灵感:

#include <cstdlib>
#include <memory>
#include <iostream>
#include <algorithm>

#include <readline/readline.h>
#include <readline/history.h>

struct malloc_deleter
{
    template <class T>
    void operator()(T* p) { std::free(p); }
};

using cstring_uptr = std::unique_ptr<char, malloc_deleter>;

std::string& trim(std::string& s, const char* t = " \t")
{
    s.erase(s.find_last_not_of(t) + 1);
    s.erase(0, s.find_first_not_of(t));
    return s;
}

int main()
{
    using_history();
    read_history(".history");

    std::string shell_prompt = "> ";

    cstring_uptr input;
    std::string line, prev;

    input.reset(readline(shell_prompt.c_str()));

    while(input && trim(line = input.get()) != "exit")
    {
        if(!line.empty())
        {
            if(line != prev)
            {
                add_history(line.c_str());
                write_history(".history");
                prev = line;
            }

            std::reverse(line.begin(), line.end());
            std::cout << line << '\n';
        }
        input.reset(readline(shell_prompt.c_str()));
    }

}

这个简单的示例只是反转您在控制台键入的所有内容。