如何在 C 中覆盖标准输出

Posted

技术标签:

【中文标题】如何在 C 中覆盖标准输出【英文标题】:How to overwrite stdout in C 【发布时间】:2010-10-13 23:29:38 【问题描述】:

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

在我看来,shell 正在以某种方式操纵 stdout 以覆盖它已经写入的内容?

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

【问题讨论】:

【参考方案1】:

它不是在操作标准输出——它是在覆盖终端已经显示的字符。

试试这个:

#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 是一个回车,终端将其解释为“将光标移回当前行的开头”。

如果是bash,您的shell 使用GNU Readline library,它提供了更通用的功能,包括检测终端类型、历史管理、可编程键绑定等。

还有一件事——如果有疑问,您的 wget、shell 等的源代码都是可用的。

【讨论】:

【参考方案2】:

要覆盖当前标准输出行(或部分),请使用\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,否则信息可能不会立即打印在输出或终端上

【讨论】:

可能想为睡眠功能添加一个#include 【参考方案3】:

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

【讨论】:

【参考方案4】:

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

如果 stdout 进入一个能理解这些转义序列的终端,终端的显示会相应改变。

如果你将标准输出重定向到一个文件,转义序列将出现在文件中(这通常不是你想要的)。

转义序列没有完整的标准,但大多数终端使用VT100引入的序列,并有很多扩展。这是 Unix/Linux 下的大多数终端(xterm、rxvt、konsole)和其他类似 PuTTY 的终端都理解的。

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

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

你可以使用回车来模拟这个。

#include <stdio.h>

int main(int argc, char* argv[])

    while(1)
    
        printf("***********");
        fflush(stdout);
        sleep(1);
        printf("\r");
        printf("...........");
        sleep(1);
    

    return 0;

【讨论】:

【参考方案7】:

程序通过打印终端以特殊方式解释的特殊字符来做到这一点。最简单的版本是(在大多数 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");

【讨论】:

处理这些特殊字符的不是shell,而是shell运行的终端,即xterm、rxvt、konsole或类似的实例。【参考方案8】:

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

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

【讨论】:

以上是关于如何在 C 中覆盖标准输出的主要内容,如果未能解决你的问题,请参考以下文章

标准 I/O 和管道

在 c/c++ 中编辑从标准输入打印在标准输出上的文本

linux学习第一周;标准输入输出和错误重定向与管道

如何在C#中重定向IronPython的标准输出?

标准I/O和管道

标准输出重定向