Подтвердить что ты не робот

Как перезаписать stdout в C

В большинстве современных оболочек вы можете нажимать стрелки вверх и вниз, и в командной строке будут введены предыдущие команды, которые вы выполнили. Мой вопрос: как это работает?!

Мне кажется, что оболочка каким-то образом управляет stdout, чтобы перезаписать то, что уже написано?

Я замечаю, что такие программы, как wget, тоже делают это. Кто-нибудь знает, как они это делают?

4b9b3361

Ответ 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').

Курсор будет перемещен в начало строки, что позволит вам перезаписать его содержимое.