【发布时间】:2015-05-24 03:51:43
【问题描述】:
我的测试应用程序将日志写入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)即可实现的解决方案。我试图用谷歌搜索,但意识到我需要一种惯用的开场白来了解我到底想要什么。
更新
感谢 Jonathan Leffler 评论,我意识到我还应该提到 分离 stderr 和 stdout 并不重要。由于我控制了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_
【问题讨论】:
-
您可以重新实现 curses 会做的事情,但其他任何事情都可能导致问题。如果你排除了最接近理智的答案,你就会遇到问题。您必须非常小心地管理写给
stderr的内容,并非常小心地与写给stdout的内容相协调。事实上,您可能需要一个全屏管理包,可能带有用于屏幕不同部分的子窗口——就像curses为您提供的那样。如果做不到这一点,您将不得不以某种方式(单独的线程?)拦截到stderr的所有输出并让它处理它。你能把错误写到日志文件吗? -
我可以在任何地方写错误,但我也需要它们在控制台上:)
-
在这种情况下,我强烈建议您使用
curses,除非您希望出于自己的目的重新实现它。否则,您必须编写代码来确定(或知道)光标的位置,将写入位置(光标)移动到您希望错误发生的行,写入错误,然后将光标移回原来的位置在你开始写错误之前。可以办到;curses提供了自动执行此操作的机制,因此您也可以执行curses所做的事情。但为自己做这件事并非易事。 -
我认为你真的需要一个 lib 或(不太便携)一个特定于操作系统的 API 来处理这类东西。否则输出将是一个连续的字符流。