Как переписать стандартный вывод в C - PullRequest
32 голосов
/ 18 марта 2009

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

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

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

Ответы [ 8 ]

43 голосов
/ 18 марта 2009

Это не манипулирование с 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, ваша оболочка и т. Д. Все доступны.

22 голосов
/ 18 марта 2009

Чтобы перезаписать текущую стандартную строку вывода (или ее части), используйте \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);, потому что стандартный вывод обычно буферизуется и в противном случае информация не может быть немедленно напечатана на выходе или терминале

12 голосов
/ 18 марта 2009

В дополнение к \ r и \ b взгляните на ncurses для некоторого расширенного контроля над тем, что находится на экране консоли. (Включая столбцы, произвольное перемещение и т. Д.).

5 голосов
/ 18 марта 2009

Программа, работающая в текстовом терминале / консоли, может манипулировать текстом, отображаемым в ее консоли, различными способами (сделать текст жирным, переместить курсор, очистить экран и т. Д.). Это достигается путем печати специальных последовательностей символов, называемых «escape-последовательности» (поскольку они обычно начинаются с Escape, ASCII 27).

Если стандартный вывод поступает на терминал, который понимает эти escape-последовательности, отображение терминала будет соответственно изменяться.

Если вы перенаправите стандартный вывод в файл, в файле появятся escape-последовательности (обычно это не то, что вам нужно).

Не существует полного стандарта для escape-последовательностей, но большинство терминалов используют последовательности, введенные VT100 , со многими расширениями. Это то, что понимают большинство терминалов под Unix / Linux (xterm, rxvt, konsole) и другие, такие как PuTTY.

На практике вы не будете напрямую жестко кодировать escape-последовательности в вашем программном обеспечении (хотя могли бы), а будете использовать библиотеку для их печати, такую ​​как ncurses или GNU readline , упомянутые выше , Это позволяет совместимость с различными типами терминалов.

2 голосов
/ 18 марта 2009

Это сделано с библиотекой readline ... Я не уверен, как это работает за кулисами, но я не думаю, что это имеет какое-либо отношение к stdout или потокам. Я подозреваю, что readline использует какие-то загадочные (по крайней мере для меня) команды терминала - то есть он взаимодействует с программой терминала, которая фактически отображает ваш сеанс оболочки. Я не знаю, что вы можете получить поведение, похожее на readline, просто распечатав вывод.

(Подумайте об этом: стандартный вывод может быть перенаправлен в файл, но трюк клавиш со стрелками вверх / вниз не работает с файлами.)

1 голос
/ 18 марта 2009

Программа делает это, печатая специальные символы, которые терминал интерпретирует особым образом. Самая простая версия этого (на большинстве терминалов linux / unix) выводит '\ r' (возврат каретки) в обычный вывод, который сбрасывает позицию курсора на первый символ в текущей строке. То, что вы напишете дальше, перезапишет строку, которую вы написали ранее. Это можно использовать, например, для простых индикаторов прогресса.

int i = 0;
while (something) {
  i++;
  printf("\rprocessing line %i...", i);
  ...
}

Но есть более сложные последовательности escape-символов, которые интерпретируются по-разному. С этим можно сделать все, например, навести курсор на определенную позицию на экране или установить цвет текста. Если или как эти последовательности символов интерпретируются, зависит от вашего терминала, но общий класс, поддерживаемый большинством терминалов, это ansi escape-последовательности . Поэтому, если вы хотите красный текст, попробуйте:

printf("Text in \033[1;31mred\033[0m\n");
1 голос
/ 18 марта 2009

Вы можете использовать возврат каретки для имитации этого.

#include <stdio.h>

int main(int argc, char* argv[])
{
    while(1)
    {
        printf("***********");
        fflush(stdout);
        sleep(1);
        printf("\r");
        printf("...........");
        sleep(1);
    }

    return 0;
}
0 голосов
/ 18 марта 2009

Простейшим способом является печать для вывода символа возврата каретки ('\ r').

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...