Стереть текущую напечатанную строку консоли
Как я могу стереть текущую напечатанную консольную строку в 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 "
Что будет делать вышеуказанная команда:
Он напечатает hello, а курсор останется на "o" (используя \c)
Затем он будет ждать 1 секунду (сон 1)
Затем он заменит 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 +++