34

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

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

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

4

8 回答 8

47

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

试试这个:

#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's 的输出,对吧? \r是一个回车,终端将其解释为“将光标移回当前行的开头”。

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

还有一件事——当你有疑问时,你的 wget、你的 shell 等的源代码都是可用的。

于 2009-03-18T00:18:13.313 回答
25

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

于 2009-03-18T00:17:32.333 回答
14

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

于 2009-03-18T00:20:38.190 回答
5

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

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

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

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

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

于 2009-03-18T01:03:14.173 回答
3

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

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

于 2009-03-18T00:14:02.713 回答
1

您可以使用回车来模拟这一点。

#include <stdio.h>

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

    return 0;
}
于 2009-03-18T00:17:50.863 回答
1

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

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

但是还有更复杂的转义字符序列,它们以各种方式解释。各种事情都可以用它来完成,比如将光标定位在屏幕上的特定位置或设置文本颜色。是否或如何解释这些字符序列取决于您的终端,但大多数终端支持的通用类是ansi 转义序列。因此,如果您想要红色文本,请尝试:

printf("Text in \033[1;31mred\033[0m\n");
于 2009-03-18T00:26:10.890 回答
0

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

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

于 2009-03-18T00:21:16.863 回答