如何在C中覆盖stdout

时间:2009-03-18 00:09:35

标签: c terminal console stdout

在大多数现代shell中,您可以点击向上和向下箭头,它会在提示符下放置您执行的先前命令。我的问题是,这是如何工作的?!

在我看来,shell以某种方式操纵stdout来覆盖已经写过的东西?

我注意到像wget这样的程序也是这样做的。有人知道他们是怎么做到的吗?

8 个答案:

答案 0 :(得分:43)

它不会操纵标准输出 - 它会覆盖已经由终端显示的字符。

试试这个:

#include <stdio.h>
#include <unistd.h>
static char bar[] = "======================================="
                    "======================================>";
int main() {
    int i;
    for (i = 77; i >= 0; i--) {
        printf("[%s]\r", &bar[i]);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return 0;
}

这非常接近wget的输出,对吗? \r是一个回车符,终端将其解释为“将光标移回当前行的开头”。

你的shell bash使用GNU Readline library,它提供更多通用功能,包括检测终端类型,历史记录管理,可编程键绑定等。

还有一件事 - 如果有疑问,你的wget,你的shell等的来源都是可用的。

答案 1 :(得分:22)

要覆盖当前标准输出行(或部分内容),请使用\r(或\b。)特殊字符\r(回车)将把插入符号返回到开头该行,允许您覆盖它。特殊字符\b只会将插入符号移回一个位置,允许您覆盖最后一个字符,例如

#include <stdio.h>
#include <unistd.h>

int i;
const char progress[] = "|/-\\";

for (i = 0; i < 100; i += 10) {
  printf("Processing: %3d%%\r",i); /* \r returns the caret to the line start */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);

printf("Processing: ");
for (i = 0; i < 100; i += 10) {
  printf("%c\b", progress[(i/10)%sizeof(progress)]); /* \b goes one back */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);

使用fflush(stdout);,因为standard output is usually buffered并且信息可能不会立即打印在输出或终端上

答案 2 :(得分:12)

除了\ r和\ b之外,请查看ncurses,以便对控制台屏幕上的内容进行一些高级控制。 (包括列,任意移动等)。

答案 3 :(得分:5)

在文本终端/控制台中运行的程序可以通过各种方式操作其控制台中显示的文本(使文本变为粗体,移动光标,清除屏幕等)。这是通过打印名为"escape sequences"的特殊字符序列来实现的(因为它们通常以Escape,ASCII 27开头)。

如果stdout进入了解这些转义序列的终端,终端的显示将相应改变。

如果将stdout重定向到文件,则转义序列将出现在文件中(通常不是您想要的)。

转义序列没有完整的标准,但大多数终端使用VT100引入的序列,并有许多扩展。这就是Unix / Linux(xterm,rxvt,konsole)下的大多数终端以及PuTTY等其他终端所理解的。

在实践中,您不会直接将转义序列硬编码到您的软件中(尽管可以),但使用库来打印它们,例如上面提到的ncursesGNU readline。这允许兼容不同的终端类型。

答案 4 :(得分:2)

已完成readline库...我不确定它是如何在幕后工作但我不认为它与stdout或流有任何关系。我怀疑readline使用某种神秘的(至少对我来说)终端命令 - 也就是说,它与实际显示你的shell会话的终端程序合作。我不知道你可以通过打印输出来获得类似readline的行为。

(考虑一下:stdout可以重定向到文件,但上/下箭头键技巧对文件不起作用。)

答案 5 :(得分:1)

您可以使用回车来模拟此事。

#include <stdio.h>

int main(int argc, char* argv[])
{
    while(1)
    {
        printf("***********");
        fflush(stdout);
        sleep(1);
        printf("\r");
        printf("...........");
        sleep(1);
    }

    return 0;
}

答案 6 :(得分:1)

程序通过打印终端以特殊方式解释的特殊字符来完成此操作。最简单的版本是(在大多数linux / unix终端上)将'\ r'(回车)打印到普通标准输出,它将光标位置重置为当前行中的第一个字符。所以你接下来写的东西会覆盖你之前写的那一行。例如,这可用于简单的进度指示器。

int i = 0;
while (something) {
  i++;
  printf("\rprocessing line %i...", i);
  ...
}

但是有更复杂的转义字符序列以各种方式解释。可以使用此功能完成各种操作,例如将光标定位在屏幕上的特定位置或设置文本颜色。是否或如何解释这些字符序列取决于您的终端,但大多数终端支持的公共类是ansi escape sequences。因此,如果您想要红色文字,请尝试:

printf("Text in \033[1;31mred\033[0m\n");

答案 7 :(得分:0)

最简单的方法是将stdout打印到回车符('\ r')。

光标将移动到行的开头,允许您覆盖其内容。