Как перезаписать stdout в C
В большинстве современных оболочек вы можете нажимать стрелки вверх и вниз, и в командной строке будут введены предыдущие команды, которые вы выполнили. Мой вопрос: как это работает?!
Мне кажется, что оболочка каким-то образом управляет stdout, чтобы перезаписать то, что уже написано?
Я замечаю, что такие программы, как wget, тоже делают это. Кто-нибудь знает, как они это делают?
Ответы
Ответ 1
Он не управляет stdout - он переписывает символы, которые уже были отображены терминалом.
Попробуйте следующее:
#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
, правильно? \r
- это возврат каретки, который терминал интерпретирует как "переместить курсор обратно в начало текущей строки".
Ваша оболочка, если она bash
, использует библиотеку GNU Readline, которая обеспечивает гораздо более общую функциональность, включая обнаружение типов терминалов, историю управление, программируемые привязки клавиш и т.д.
Еще одна вещь - когда есть сомнения, источник для вашего wget, вашей оболочки и т.д. доступны.
Ответ 2
Чтобы перезаписать текущую стандартную выходную строку (или ее части), используйте \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);
, потому что стандартный вывод обычно буферизируется, и информация не может быть немедленно напечатана на выходе или терминале
Ответ 3
В дополнение к \r и\b посмотрите ncurses для некоторого расширенного контроля над тем, что на экране консоли. (Включая столбцы, перемещаясь произвольно и т.д.).
Ответ 4
Программа, работающая в текстовом терминале/консоли, может манипулировать текстом, отображаемым на его консоли, различными способами (сделать текст полужирным, переместить курсор, очистить экран и т.д.). Это достигается путем печати специальных последовательностей символов, называемых "escape-последовательностей" (поскольку они обычно начинаются с Escape, ASCII 27).
Если stdout переходит к терминалу, который понимает эти escape-последовательности, отображение терминала будет соответствующим образом изменяться.
Если вы перенаправляете stdout в файл, в файле будут отображаться escape-последовательности (что обычно не то, что вы хотите).
Нет полного стандарта для escape-последовательностей, но большинство терминалов используют последовательности, введенные VT100, со многими расширениями. Это то, что понимают большинство терминалов под Unix/Linux (xterm, rxvt, konsole) и другие, такие как PuTTY.
На практике вы не будете напрямую жестко кодировать escape-последовательности в свое программное обеспечение (хотя можете), но используйте библиотеку для их печати, например ncurses или GNU readline, упомянутых выше. Это позволяет совместимость с различными типами терминалов.
Ответ 5
Это сделано с помощью readline библиотеки... Я не уверен, как это работает за кулисами, но я не думаю, что это имеет какое-либо отношение к stdout или потокам. Я подозреваю, что readline использует какие-то загадочные (по крайней мере, для меня) терминальные команды, то есть он взаимодействует с терминальной программой, которая фактически отображает ваш сеанс оболочки. Я не знаю, что вы можете получить поведение, подобное чтению, только путем печати вывода.
(Подумайте об этом: stdout можно перенаправить в файл, но трюк клавиш вверх/вниз не работает с файлами.)
Ответ 6
Программа делает это путем печати специальных символов, которые терминал интерпретирует особым образом. Самая простая версия этого (на большинстве терминалов linux/unix) печатает '\ r' (возврат каретки) к нормальному stdout, который сбрасывает позицию курсора на первый символ в текущей строке. Итак, то, что вы пишете, будет перезаписано ранее написанной вами линией. Например, это можно использовать для простых индикаторов прогресса.
int i = 0;
while (something) {
i++;
printf("\rprocessing line %i...", i);
...
}
Но существуют более сложные последовательности escape-символов, которые интерпретируются различными способами. Для этого можно сделать все возможное, например, позиционировать курсор в определенной позиции на экране или установить цвет текста. Если интерпретация этих последовательностей символов или их интерпретация зависит от вашего терминала, но общий класс, поддерживаемый большинством терминалов, ansi escape-последовательности. Поэтому, если вам нужен красный текст, попробуйте:
printf("Text in \033[1;31mred\033[0m\n");
Ответ 7
Вы можете использовать возврат каретки, чтобы имитировать это.
#include <stdio.h>
int main(int argc, char* argv[])
{
while(1)
{
printf("***********");
fflush(stdout);
sleep(1);
printf("\r");
printf("...........");
sleep(1);
}
return 0;
}
Ответ 8
Простейшим способом является печать на stdout символа возврата каретки ('\ r').
Курсор будет перемещен в начало строки, что позволит вам перезаписать его содержимое.