【问题标题】:Erase the current printed console line擦除当前打印的控制台行
【发布时间】:2010-12-03 06:28:27
【问题描述】:

如何在 C 中擦除当前打印的控制台行?我正在使用 Linux 系统。例如 -

printf("hello");
printf("bye");

我想在同一行打印 bye 来代替 hello。

【问题讨论】:

  • ncurses 库可能会这样做。我不太了解,无法提供答案,但这里有指向 docsa related question 的链接。此评论基于 Wil Shipley 2009 年的回答,可能会被删除。

标签: c linux console erase


【解决方案1】:

您可以使用VT100 escape codes。大多数终端,包括 xterm,都支持 VT100。对于擦除一行,这是^[[2K。在 C 中,这给出了:

printf("\33[2K\r");

【讨论】:

  • 这不会将光标重置到行首(至少在我的终端中),所以仍然需要\r。请注意,不需要使用%c。备选:printf("\33[2K\r");
  • 请注意,Windows 不支持 ANSI 格式 (ANSI escape code)。但仍然可以使用printf("\r ");
  • @CocoaBob 从 Windows 10 更新 TH2 开始有 changed
  • Windows 10之前的 Windows 上不工作
  • @LưuVĩnhPhúc - 问题结束于:我正在使用 linux 系统
【解决方案2】:

其他人已经回答了 OP 的问题。对于那些想知道为什么回车在他们的 Linux 机器上的行为方式的人来说,这是一个答案 -

回车符的行为似乎是平台相关的。

来自 C11 标准的“5.2.2 字符显示语义”部分:

\r (回车) 将活动位置移动到初始位置 当前行的。

来自 POSIX 标准 (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html) 的“3.86 回车符 ()”部分:

输出流中的一个字符表示打印应该 从同一物理行的开头开始,其中 发生回车。它是 '\r' 指定的字符 C语言。未指定此字符是否准确 由系统发送到输出设备的序列来完成 移动到行首。

它没有说明回车是否应该删除(=> 填充 NUL 字符)整行。我的猜测是它不应该被擦除。

但是,在我的 Linux 机器上(在 x86_64 和 ARM32 上都试过),我观察到回车符将光标移动到当前行的开头,并且还用 '\0' 字符填充了该行(NUL人物)。为了注意那些 NUL 字符,您可能必须直接从您的代码中调用 write 系统调用,而不是通过 glibc printf 调用。

我们以下面的代码sn-p为例:

printf("hello");
printf("\rbye");

在 beaglebone black(32 位 ARM)bash 终端上构建和运行它:

ubuntu@arm:~$ ./a.out 
byeubuntu@arm:~$ 

写入系统调用时的 strace 输出:

bye)               = 9 9hello
+++ exited with 4 +++

【讨论】:

  • 我没有看到 NUL 字符的证据,也不应该有。
【解决方案3】:

一些有价值的微妙之处......

\33[2K 擦除光标当前所在的整行

\033[A 将光标向上移动一行,但在同一列,即不在行首

\r 将光标移至行首(r 用于回车 NB 回车不包含换行符,因此光标保持在同一行)但 删除任何内容

特别是在 xterm 中,我尝试了上面提到的回复,我发现删除该行并从头开始重新开始的唯一方法是序列(来自@Stephan202 以及@vlp 和@mantal 发布的上述评论) \33[2K\r

在实现说明中,为了让它在例如倒计时场景中正常工作,因为我没有使用换行符'\n' 在每个fprintf() 的末尾,所以我每次都必须fflush() 流(为了给你一些上下文,我在Linux 机器上使用fork 启动xterm 而不重定向stdout,我只是在写入缓冲的文件指针fdfile 带有非阻塞文件描述符,我坐在伪终端地址上,在我的例子中是 /dev/pts/21):

fprintf(fdfile, "\33[2K\rT minus %d seconds...", i);
fflush(fdfile);

请注意,我使用了 \33[2K 序列来擦除行,然后使用\r 回车序列将光标重新定位在行首。我不得不在每个fprintf() 之后@ 987654332@,因为我在'\n' 末尾没有换行符。不需要 fflush() 的相同结果将需要额外的序列才能上一行:

fprintf(fdfile, "\033[A\33[2K\rT minus %d seconds...\n", i);

请注意,如果您在要写入的行的正上方有内容,它将被第一个 fprintf() 覆盖。您必须在上面多留一行,以便第一次向上移动一行:

i = 3;
fprintf(fdfile, "\nText to keep\n");
fprintf(fdfile, "Text to erase****************************\n");
while(i > 0) { // 3 second countdown
    fprintf(fdfile, "\033[A\33[2KT\rT minus %d seconds...\n", i);
    i--;
    sleep(1);
}

【讨论】:

  • \r 是“回车”的缩写,而不是“倒带”,是老式打字机的遗物,它有单独的“将光标移回第一列”操作(\r ) 和“将页面前进到下一行” (\n)。
  • \33[2K 是一条路!对我来说最好的问题解决方案。也适用于 Windows ConEmu
  • 由于某种原因,当我在终端(bash、zsh 等)中的 MacOS 上运行简单的 C 可执行文件时,所有这些精彩的答案都对我不起作用。我得到了各种奇怪的行为,并且打印了奇怪的字符(或根本没有打印),但从来没有打印出清晰的线条等。
【解决方案4】:

在 windows 10 下,可以通过激活当前控制台中的 VT100 模式来使用 VT100 样式,以使用如下转义序列:

#include <windows.h>
#include <iostream>

#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#define DISABLE_NEWLINE_AUTO_RETURN  0x0008

int main(){

  // enabling VT100 style in current console
  DWORD l_mode;
  HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
  GetConsoleMode(hStdout,&l_mode)
  SetConsoleMode( hStdout, l_mode |
            ENABLE_VIRTUAL_TERMINAL_PROCESSING |
            DISABLE_NEWLINE_AUTO_RETURN );

  // create a waiting loop with changing text every seconds
  while(true) {
    // erase current line and go to line begining 
    std::cout << "\x1B[2K\r";
    std::cout << "wait a second .";
    Sleep(1);
    std::cout << "\x1B[2K\r";
    std::cout << "wait a second ..";
    Sleep(1);
    std::cout << "\x1B[2K\r";
    std::cout << "wait a second ...";
    Sleep(1);
    std::cout << "\x1B[2K\r";
    std::cout << "wait a second ....";
 }

}

见以下链接:windows VT100

【讨论】:

    【解决方案5】:
    echo -e "hello\c" ;sleep 1 ; echo -e "\rbye  "
    

    上面的命令会做什么:

    1. 它将打印 hello 并且光标将保持在“o”(使用 \c)

    2. 然后它会等待 1 秒(睡眠 1)

    3. 然后它将把hello替换为bye。(使用\r)

    NOTE : Using ";", We can run multiple command in a single go.

    【讨论】:

    • 这是 bash,不是 C
    【解决方案6】:

    刚刚找到这个旧线程,正在寻找某种转义序列来空白实际行。

    很有趣,没有人想到 printf 返回写入的字符数(或者我错过了)。因此,只需打印 '\r' + 与 printf 返回一样多的空白字符,您将完全空白先前编写的文本。

    int BlankBytes(int Bytes)
    {
                    char strBlankStr[16];
    
                    sprintf(strBlankStr, "\r%%%is\r", Bytes);
                    printf(strBlankStr,"");
    
                    return 0;
    }
    
    int main(void)
    {
                    int iBytesWritten;
                    double lfSomeDouble = 150.0;
    
                    iBytesWritten = printf("test text %lf", lfSomeDouble);
    
                    BlankBytes(iBytesWritten);
    
                    return 0;
    }
    

    由于我无法使用 VT100,看来我必须坚持使用该解决方案

    【讨论】:

      【解决方案7】:

      您可以在这里使用一个简单的技巧,但需要在打印之前进行准备,您必须将想要打印的内容放入变量中然后打印,这样您就会知道删除字符串的长度。这里是例子。

      #include <iostream>
      #include <string> //actually this thing is not nessasory in tdm-gcc
      
      using namespace  std;
      
      int main(){
      
      //create string variable
      
      string str="Starting count";
      
      //loop for printing numbers
      
          for(int i =0;i<=50000;i++){
      
              //get previous string length and clear it from screen with backspace charactor
      
              cout << string(str.length(),'\b');
      
              //create string line
      
              str="Starting count " +to_string(i);
      
              //print the new line in same spot
      
              cout <<str ;
          }
      
      }
      

      【讨论】:

        【解决方案8】:

        此脚本已针对您的示例进行了硬编码。

        #include <stdio.h>
        
        int main ()
        {
        
            //write some input  
            fputs("hello\n",stdout);
        
            //wait one second to change line above
            sleep(1);
        
            //remove line
            fputs("\033[A\033[2K",stdout);
            rewind(stdout);
        
            //write new line
            fputs("bye\n",stdout);
        
            return 0;
        }
        

        点击这里查看source

        【讨论】:

          【解决方案9】:

          i 遍历 char 数组字。 j 跟踪字长。 "\b \b" 在倒行时擦除单词。

          #include<stdio.h>
          
          int main()
          {
              int i = 0, j = 0;
          
              char words[] = "Hello Bye";
          
              while(words[i]!='\0')
              {
                  if(words[i] != ' ') {
                      printf("%c", words[i]);
                  fflush(stdout);
                  }
                  else {
                      //system("ping -n 1 127.0.0.1>NUL");  //For Microsoft OS
                      system("sleep 0.25");
                      while(j-->0) {
                          printf("\b \b");
                      }
                  }
          
                  i++;
                  j++;
              }
          
          printf("\n");                   
          return 0;
          }
          

          【讨论】:

            【解决方案10】:

            您可以使用 \b 删除该行

            printf("hello");
            int i;
            for (i=0; i<80; i++)
            {
              printf("\b");
            }
            printf("bye");
            

            【讨论】:

            • 不 \b 只是将光标移回一个字符吗?人物仍然会在那里。我猜你可以做类似“\b \b”的事情。
            • 在我的 Linux 机器上,该行被删除了。可能在其他平台上的行为有所不同。
            【解决方案11】:

            您可以使用\r (carriage return) 将光标返回到行首:

            printf("hello");
            printf("\rbye");
            

            这将在同一行打印 bye。但是它不会删除现有的字符,并且因为 byehello 短,所以你最终会得到 byelo。要删除它,您可以延长新打印的时间以覆盖多余的字符:

            printf("hello");
            printf("\rbye  ");
            

            或者,先用几个空格擦除它,然后打印你的新字符串:

            printf("hello");
            printf("\r          ");
            printf("\rbye");
            

            这将打印 hello,然后转到行首并用空格覆盖它,然后再次回到开头并打印 bye

            【讨论】:

            • "虽然它不会删除现有的字符,而且因为 bye 比 hello 短,所以你最终会得到 byelo。" -> 也许它依赖于平台?检查我的答案:stackoverflow.com/a/68294831/4726668
            【解决方案12】:

            通常,当字符串末尾有一个 '\r' 时,只打印回车而不打印任何换行符。如果您有以下情况:

            printf("fooooo\r");
            printf("bar");
            

            输出将是:

            barooo
            

            我可以建议的一件事(也许是一种解决方法)是有一个以 NULL 结尾的固定大小字符串,该字符串初始化为所有空格字符,以 '\r' 结尾(每次打印前),然后使用 strcpy 复制将您的字符串放入其中(没有换行符),因此每次后续打印都将覆盖前一个字符串。像这样的:

            char str[MAX_LENGTH];        
            // init str to all spaces, NULL terminated with character as '\r'
            strcpy(str, my_string);       // copy my_string into str
            str[strlen(my_string)] = ' '; // erase null termination char
            str[MAX_LENGTH - 1] = '\r';
            printf(str);
            

            您可以进行错误检查,以使my_string 的长度始终比str 少一个,但您了解基本概念。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-07-21
              • 2016-07-14
              • 2010-09-30
              • 2014-09-04
              • 2021-10-15
              • 1970-01-01
              • 2011-07-07
              • 1970-01-01
              相关资源
              最近更新 更多