Страницы

Поиск по вопросам

среда, 25 декабря 2019 г.

Восcтановить данные из потока ввода

#linux #c


Пользователь вводит информацию в терминале, в это время с другого процесса программы
приходит сообщение и затирает данные, которые пользователь уже ввёл, но не отправил. 

Как после этого восстановить затертые данные и поместить их на следующую строку за
пришедшим сообщением? 

#include 
#include 
#include 

#include 

#define MAX_LEN 25

int main(int argc, char *argv[])
{
    char message[MAX_LEN];
    char answer[MAX_LEN] = "test\n";
    pid_t pid;

    pid = fork();

    if (pid < 0) {
        perror("fork()");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        read(STDIN_FILENO, message, MAX_LEN);        
    }

    if (pid > 0) {
        while (1) {
            sleep(2);
            write(STDOUT_FILENO, "\r", 1);
            write(STDOUT_FILENO, answer, strlen(answer));
        }
    }

    return 0;
}

    


Ответы

Ответ 1



Пример где это реализовано: наберите в командной строке sleep 2 и нажмите Enter, теперь пока команда sleep выполняется наберите что-то, но enter не нажимайте. Когда bash проснётся, он выводит приглашение а потом ваш ввод. Более простой интерпретатор dash так не умеет. Секрет состоит в отключении режима терминала ECHO, который повторяет вводимые символы, включении режима посимвольного ввода и реализации отображения вводимых символов своими силами либо с помощью библиотеки, например GNU Readline. В режиме посимвольного ввода read() будет возвращать результат, когда была нажата хотя бы одна клавиша (правда ещё зависит от размера буфера и параметров VMIN и VTIME, но в любом случае ожидается хотя бы один байт). Смотрите документацию в man tcsetattr. #include #include ... struct termios t; tcgetattr(2, &t); tcflag_t oldtcflags = t.c_lflag; t.c_lflag &= ~( ECHO | ICANON ); tcsetattr(2, TCSANOW, &t); // буфер ввода и позиция "курсора" в нём, первый символ - возврат в начало строки char input[80]="\r", i=1; // структура для poll, который используем перед read чтобы ждать ввода не более 1 секунды struct pollfd pollev= {fd:0, events:POLLIN}; // самый тупой редактор тупо заполняет буфер и повторяет его на вывод while(i<79) { char b; if(poll(&pollev, 1, 1000)) { if(read(0, &b, 1)!=1) break; input[i++]= b; } // если ничего не введено за одну секунду, всё равно обновим строку, вдруг сообщение испортило экран write(2, input, i); if(b=='\n') break; } input[i]=0; // Восстановление старого режима терминала t.c_lflag = oldtcflags; tcsetattr(2, TCSANOW, &t); // Далее в буфере input первый символ следует игнорировать ... Данная программа обновляет вводимую строку на экране раз в секунду и каждый раз после ввода очередного символа. Редактор рассчитан на то что stderr (он же дескриптор 2) связан с тем же терминалом, что и stdin (он же дескриптор 0). Если это не так (пользователь решил перенаправить вывод stderr), он вероятнее всего не увидит наши символы при вводе (если только он не использовал трубу в tee или что-то подобное). Т.е. в случае перенаправления stderr мы можем потерять эмуляцию эха. Поэтому я использовал тот же дескриптор для tcgetattr/tcsetattr: в случае перенаравления в файл или трубу эти функции не сработают и останется обычный построчный режим с эхом. В обычной командной строке все три дескриптора на самом деле доступны и на чтение и на запись (возможно в каких-то системах это не так). Полная эмуляция будет, если использовать дескриптор 0 для всех операций в редакторе (read, write, poll, tcgetattr, tcsetattr), это позволит пользователю перенаправлять вывод как угодно, если же он перенаправит ввод, write и управление терминалом не сработают. Вроде бы то, что нужно, но как мне кажется основано на недокументированных возможностях.

Комментариев нет:

Отправить комментарий