Стереть текущую напечатанную строку консоли

Как я могу стереть текущую напечатанную консольную строку в C? Я работаю в системе Linux. Например -

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

Я хочу напечатать пока на той же строке вместо Привет.

15 ответов

Решение

Вы можете использовать escape-коды VT100. Большинство терминалов, включая xterm, поддерживают VT100. Для стирания строки это ^[[2K, В Си это дает:

printf("%c[2K", 27);

Некоторые стоящие тонкости...

\33[2K стирает всю строку, на которой находится ваш курсор

\033[A перемещает курсор вверх на одну строку, но в том же столбце, т.е. не в начало строки

\r переводит курсор в начало строки (r для перемотки назад), но ничего не стирает

В частности, в xterm я попробовал ответы, упомянутые выше, и единственный найденный способ стереть строку и начать сначала с начала - это последовательность (из комментария выше, опубликованного @Stephan202, а также @vlp и @mantal) \33[2K\r

На заметку о реализации, чтобы заставить его работать должным образом, например, в сценарии обратного отсчета, так как я не использовал символ новой строки '\n'в конце каждого fprintf()так что мне пришлось fflush() поток каждый раз (чтобы дать вам некоторый контекст, я запустил xterm с помощью fork на машине linux без перенаправления stdout, я просто писал в буферизованный указатель FILE fdfile с неблокирующим файловым дескриптором я сидел по адресу псевдотерминала, который в моем случае был /dev/pts/21):

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

Обратите внимание, что я использовал последовательность \33[2K, чтобы стереть строку, за которой следует \r последовательность перемотки, чтобы переместить курсор в начало строки. Мне пришлось fflush() после каждого fprintf() потому что у меня нет символа новой строки в конце '\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 ( возврат каретки), чтобы вернуть курсор в начало строки:

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

Это напечатает пока на той же самой линии. Это не сотрет существующие символы, и, поскольку пока короче, чем привет, вы получите в итоге пока. Чтобы стереть его, вы можете сделать новый отпечаток длиннее, чтобы перезаписать дополнительные символы:

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

Или сначала сотрите его несколькими пробелами, а затем напечатайте новую строку:

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

Это напечатает привет, затем перейдите к началу строки и перезапишите ее пробелами, затем вернитесь к началу снова и напечатайте пока.

Вы можете удалить строку, используя \b

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

Обычно, когда в конце строки стоит символ "\ 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, но вы получите основную идею.

i перебирает слова массива символов 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;
}

Этот скрипт жестко запрограммирован для вашего примера.

#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;
}

Нажмите здесь для источника.

В 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

      printf("\x1b[1F");

Управление курсором ANSI здесь\x1bэто escape-код и и[#Fпредставляет - перейти в начало # строк вверх.

Здесь есть простой трюк, с которым вы можете работать, но он требует подготовки перед печатью, вы должны поместить в переменную то, что хотите печатать, а затем печатать, чтобы вы знали длину удаления строки. Вот пример.

#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 ;
    }

}

используйте эту функцию, чтобы очиститьnстроки на С++

      void clear_line(int n) {
    std::string line_up = "\x1b[A";
    std::string line_clear = "\33[2K\r";
    for (int i = 0; i < n; ++i)
        std::cout << line_up << line_clear << std::flush;
}
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.

Просто нашел этот старый поток, искал какую-то escape-последовательность, чтобы очистить фактическую строку.

Довольно забавно, что никто не пришел к мысли (или я пропустил это), что 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, кажется, я должен придерживаться этого решения

Для меня этот код хорошо работает для окна последовательной консоли с Arduino на консоли Tera Term VT:

      SEROUT.print("\e[A\r\n\e[2K");
SEROUT.print('>');

Я использую '>', потому что в моей консольной команде я набираю команду после '>'

Другие уже ответили на вопрос OP. Вот ответ для тех, кто задается вопросом, почему возврат каретки ведет себя так же, как на их Linux-машине:

Поведение символа возврата каретки, похоже, зависит от платформы.

Из раздела «5.2.2 Семантика отображения символов» стандарта C11:

\r (возврат каретки) Перемещает активную позицию в начальную позицию текущей строки.

Из раздела «3.86 символ возврата каретки (<возврат каретки>)» стандарта POSIX (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html):

Символ в выходном потоке указывает, что печать должна начинаться с начала той же физической строки, в которой произошел возврат каретки. Это символ, обозначенный '\r' на языке C. Не указано, является ли этот символ точной последовательностью, передаваемой системой на устройство вывода для выполнения перемещения к началу строки.

Он не указывает, должен ли возврат каретки стирать (=> заполнять NUL-символы) всю строку или нет. Я предполагаю, что он НЕ должен стираться.

Однако на моей машине Linux (пробованной как на x86_64, так и на ARM32) я заметил, что символ возврата каретки перемещал курсор в начало текущей строки, а также заполнял строку символами '\0' (символами NUL). Чтобы заметить эти символы NUL, вам, возможно, придется вызвать системный вызов write прямо из вашего кода, а не через glibc printf.

В качестве примера возьмем следующий фрагмент кода:

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

Создание и запуск этого на bash-терминале beaglebone black (32-битная ARM):

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

Вывод strace при системном вызове записи:

      bye)               = 9 9hello
+++ exited with 4 +++
Другие вопросы по тегам