Как избежать нажатия Enter с помощью getchar()

В следующем коде:

#include <stdio.h>

int main(void) {   
  int c;   
  while ((c=getchar())!= EOF)      
    putchar(c); 
  return 0;
}

Я должен нажать Enter, чтобы напечатать все буквы, которые я ввел с помощью getchar, но я не хочу этого делать. Я хочу нажать на букву и сразу же увидеть введенную мною букву. повторяется без нажатия Enter. Например, если я нажимаю букву "а", я хочу видеть рядом с ней другую букву "а" и т.д.

aabbccddeeff.....

Но когда я нажимаю "а", ничего не происходит, я могу писать другие буквы, и копия появляется только тогда, когда я нажимаю Enter:

abcdef
abcdef

Как я могу это сделать?

Я использую команду cc -o example example.c под Ubuntu для компиляции.

Ответ 1

В системе Linux вы можете изменить поведение терминала с помощью команды stty. По умолчанию терминал буферизует всю информацию до тех пор, пока не будет нажата кнопка Enter, даже не отправив ее в программу C.

Быстрый, грязный и не особо переносимый пример изменения поведения внутри самой программы:

#include<stdio.h>
#include<stdlib.h>

int main(void){
  int c;
  /* use system call to make terminal send all keystrokes directly to stdin */
  system ("/bin/stty raw");
  while((c=getchar())!= '.') {
    /* type a period to break out of the loop, since CTRL-D won't work raw */
    putchar(c);
  }
  /* use system call to set terminal behaviour to more normal behaviour */
  system ("/bin/stty cooked");
  return 0;
}

Обратите внимание, что это не совсем оптимальный вариант, поскольку он просто предполагает, что stty cooked - это поведение, которое вы хотите при выходе из программы, а не проверка исходных настроек терминала. Кроме того, поскольку вся специальная обработка пропускается в необработанном режиме, многие последовательности клавиш (например, CTRL-C или CTRL-D) фактически не будут работать так, как вы ожидаете, без явной обработки их в программе.

Вы можете man stty для большего контроля над поведением терминала, в зависимости от того, чего именно вы хотите достичь.

Ответ 2

Это зависит от вашей ОС: если вы находитесь в UNIX-подобной среде, флаг ICANON включен по умолчанию, поэтому ввод буферизуется до следующего '\n' или EOF. Отключив канонический режим, вы сразу получите персонажей. Это также возможно на других платформах, но нет прямого кросс-платформенного решения.

ОБНОВЛЕНИЕ: я вижу, вы указали, что вы используете Ubuntu. Я только что опубликовал нечто подобное вчера, но учтите, что это отключит многие стандартные настройки вашего терминала.

#include<stdio.h>
#include <termios.h>            //termios, TCSANOW, ECHO, ICANON
#include <unistd.h>     //STDIN_FILENO


int main(void){   
    int c;   
    static struct termios oldt, newt;

    /*tcgetattr gets the parameters of the current terminal
    STDIN_FILENO will tell tcgetattr that it should write the settings
    of stdin to oldt*/
    tcgetattr( STDIN_FILENO, &oldt);
    /*now the settings will be copied*/
    newt = oldt;

    /*ICANON normally takes care that one line at a time will be processed
    that means it will return if it sees a "\n" or an EOF or an EOL*/
    newt.c_lflag &= ~(ICANON);          

    /*Those new settings will be set to STDIN
    TCSANOW tells tcsetattr to change attributes immediately. */
    tcsetattr( STDIN_FILENO, TCSANOW, &newt);

    /*This is your part:
    I choose 'e' to end input. Notice that EOF is also turned off
    in the non-canonical mode*/
    while((c=getchar())!= 'e')      
        putchar(c);                 

    /*restore the old settings*/
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt);


    return 0;
}

Вы заметите, что каждый персонаж появляется дважды. Это связано с тем, что вход немедленно возвращается на терминал, а затем ваша программа также возвращает его с помощью putchar(). Если вы хотите отключить вход от выхода, вы также должны повернуть флаг ECHO. Вы можете сделать это, просто изменив соответствующую строку на:

newt.c_lflag &= ~(ICANON | ECHO); 

Ответ 3

getchar() - стандартная функция, которая на многих платформах требует, чтобы вы нажимали ENTER, чтобы получить вход, потому что буферы платформы вводятся до тех пор, пока эта клавиша не будет нажата. Многие компиляторы/платформы поддерживают нестандартный getch(), который не заботится о ENTER (обходит буферизацию платформы, обрабатывает ENTER как просто еще один ключ).

Ответ 4

I/O - это функция операционной системы. Во многих случаях операционная система не передает типизированный символ программе, пока не будет нажата клавиша ENTER. Это позволяет пользователю изменять входные данные (например, обратную опцию и повторный набор) перед отправкой в ​​программу. Для большинства целей это хорошо работает, представляет собой согласованный интерфейс для пользователя и освобождает программу от необходимости иметь дело с этим. В некоторых случаях желательно, чтобы программа получала символы от клавиш при нажатии.

Библиотека C сама занимается файлами и не относится к тому, как данные попадают во входной файл. Поэтому на самом языке нет способа получить ключи по мере их нажатия; вместо этого это зависит от платформы. Поскольку вы не указали ОС или компилятор, мы не можем найти его для вас.

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

Ответ 5

Поскольку вы работаете над производным Unix (Ubuntu), вот один из способов сделать это - не рекомендуется, но он будет работать (пока вы можете точно набирать команды):

echo "stty -g $(stty -g)" > restore-sanity
stty cbreak
./your_program

Используйте прерывание, чтобы остановить программу, когда вам это надоедает.

sh restore-sanity
  • Строка 'echo' сохраняет текущие настройки терминала в виде оболочки script, которая их восстановит.
  • Строка 'stty' отключает большую часть специальной обработки (поэтому Control-D не действует, например) и отправляет символы в программу, как только они будут доступны. Это означает, что вы больше не можете редактировать свой ввод.
  • Строка 'sh' восстанавливает исходные настройки терминала.

Вы можете экономить, если "stty sane" восстанавливает ваши настройки достаточно точно для ваших целей. Формат "-g" не переносится в разных версиях "stty" (поэтому то, что генерируется в Solaris 10, не будет работать на Linux или наоборот), но концепция работает повсеместно. Опция "stty sane" не универсальна, AFAIK (но находится в Linux).

Ответ 6

Мне нравится, что Лукас отвечает, но я хотел бы кое-что уточнить. В termios.h есть встроенная функция с именем cfmakeraw(), которую человек описывает как:

cfmakeraw() sets the terminal to something like the "raw" mode of the
old Version 7 terminal driver: input is available character by
character, echoing is disabled, and all special processing of
terminal input and output characters is disabled. [...]

Это в основном делает то же, что и предложенный Лукасом, и вы можете видеть точные флаги, которые он устанавливает на страницах man: termios (3).

Использовать регистр

int c = 0;
static struct termios oldTermios, newTermios;

tcgetattr(STDIN_FILENO, &oldTermios);
newTermios = oldTermios;

cfmakeraw(&newTermios);

tcsetattr(STDIN_FILENO, TCSANOW, &newTermios);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios);

switch (c) {
    case 113: // q
        printf("\n\n");
        exit(0);
        break;
    case 105: // i
        printf("insert\n");
        break;
    default:
        break;

Ответ 7

Вы можете включить библиотеку "ncurses" и использовать getch() вместо getchar().

Ответ 8

да, вы можете сделать это и в окнах, вот код ниже, используя библиотеку conio.h

#include <iostream> //basic input/output
#include <conio.h>  //provides non standard getch() function
using namespace std;

int main()
{  
  cout << "Password: ";  
  string pass;
  while(true)
  {
             char ch = getch();    

             if(ch=='\r'){  //when a carriage return is found [enter] key
             cout << endl << "Your password is: " << pass <<endl; 
             break;
             }

             pass+=ch;             
             cout << "*";
             }
  getch();
  return 0;
}

Ответ 9

У меня возникла эта проблема/вопрос в задании, над которым я сейчас работаю. Это также зависит от того, с какого входа вы захватываете. Я использую

/dev/tty

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

На машине ubuntu мне нужно протестировать/настроить, для этого требуется больше, чем просто

system( "stty -raw" );

или

system( "stty -icanon" );

Мне пришлось добавить флаг -file, а также путь к команде:

system( "/bin/stty --file=/dev/tty -icanon" );

Теперь все копакетично.

Ответ 10

По умолчанию библиотека C буферизует вывод до тех пор, пока не увидит возврат. Чтобы немедленно распечатать результаты, используйте fflush:

while((c=getchar())!= EOF)      
{
    putchar(c);
    fflush(stdout);
}