在大多数现代 shell 中,您可以点击向上和向下箭头,它会在提示符下显示您之前执行的命令。我的问题是,这是如何工作的?!
在我看来,shell 以某种方式操纵 stdout 以覆盖它已经编写的内容?
我注意到像 wget 这样的程序也可以做到这一点。有人知道他们是怎么做到的吗?
它不是在操作标准输出——它是在覆盖终端已经显示的字符。
试试这个:
#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 等的源代码都是可用的。
要覆盖当前标准输出行(或它的一部分),请使用\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);
因为标准输出通常是缓冲的,否则信息可能不会立即打印在输出或终端上
除了 \r 和 \b 之外,请查看ncurses以对控制台屏幕上的内容进行一些高级控制。(包括列,随意移动等)。
在文本终端/控制台中运行的程序可以以各种方式操作在其控制台中显示的文本(使文本变为粗体、移动光标、清除屏幕等)。这是通过打印特殊字符序列来完成的,称为“转义序列”(因为它们通常以 Escape,ASCII 27 开头)。
如果 stdout 进入一个能够理解这些转义序列的终端,终端的显示将相应地改变。
如果您将 stdout 重定向到文件,转义序列将出现在文件中(这通常不是您想要的)。
转义序列没有完整的标准,但大多数终端使用VT100引入的序列,并有很多扩展。这是 Unix/Linux 下的大多数终端(xterm、rxvt、konsole)和 PuTTY 等其他终端所理解的。
在实践中,您不会直接将转义序列硬编码到您的软件中(尽管您可以),而是使用库来打印它们,例如上面提到的ncurses或GNU readline。这允许与不同的终端类型兼容。
它是用readline库完成的......我不确定它在幕后是如何工作的,但我认为它与 stdout 或流没有任何关系。我怀疑 readline 使用了某种神秘的(至少对我而言)终端命令——也就是说,它与实际显示你的 shell 会话的终端程序合作。我不知道您是否可以仅通过打印输出来获得类似 readline 的行为。
(想一想:stdout 可以重定向到文件,但上/下箭头键的技巧不适用于文件。)
您可以使用回车来模拟这一点。
#include <stdio.h>
int main(int argc, char* argv[])
{
while(1)
{
printf("***********");
fflush(stdout);
sleep(1);
printf("\r");
printf("...........");
sleep(1);
}
return 0;
}
该程序通过打印终端以特殊方式解释的特殊字符来做到这一点。最简单的版本是(在大多数 linux/unix 终端上)将 '\r' (回车)打印到普通标准输出,它将光标位置重置为当前行的第一个字符。所以你接下来写的东西会覆盖你之前写的那行。例如,这可用于简单的进度指示器。
int i = 0;
while (something) {
i++;
printf("\rprocessing line %i...", i);
...
}
但是还有更复杂的转义字符序列,它们以各种方式解释。各种事情都可以用它来完成,比如将光标定位在屏幕上的特定位置或设置文本颜色。是否或如何解释这些字符序列取决于您的终端,但大多数终端支持的通用类是ansi 转义序列。因此,如果您想要红色文本,请尝试:
printf("Text in \033[1;31mred\033[0m\n");
最简单的方法是将回车符 ('\r') 打印到标准输出。
光标将移动到行首,允许您覆盖其内容。