調べたこと、作ったことをメモしています。
こちらに移行中: https://blog.shimazu.me/

getcharをnonblockingで行う ( ターミナルからの入力で改行を待たない )

概要
通常、getchar()とやっても改行を入力するまでは待ち、改行を入力するとドバっと複数文字が処理される。バッファリングされているんだろうとfflushやsetbufをしても無理。ということで、ターミナル側でバッファリングされているんだろうということは検討がつくが、実際に設定するほうほうを調べてみた。先日のリンク( ncurses )もそれに対する対処だが、今回の方法のほうがお手軽だと思う。

参考
Cでnon-blocking IO
termios.hの解説

テストコードについて
やってることは単純で、tcsetattrという関数でsettingsを設定しているだけ。その時に2つのフラグの設定をdisableにしている。

ECHO : エコーバック(打ったキーがターミナルに出ること)を行う
ICANON : canonicalモードにする。

canonicalモードに関してはman tcsetattrとかすると詳しい内容が出てくる。

   Canonical and noncanonical mode
     The setting of the ICANON canon flag in c_lflag determines whether the terminal is operating in canonical mode (ICANON set) or noncanonical mode (ICANON unset).  By default, ICANON set.

In canonical mode:

* Input is made available line by line.  An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line).  Except in the case of EOF, the  line
       delimiter is included in the buffer returned by read(2).

* Line  editing  is  enabled  (ERASE,  KILL; and if the IEXTEN flag is set: WERASE, REPRINT, LNEXT).  A read(2) returns at most one line of input; if the read(2) requested fewer bytes than are
       available in the current line of input, then only as many bytes as requested are read, and the remaining characters will be available for a future read(2).

      In noncanonical mode input is available immediately (without the user having to type a line-delimiter character), and line editing is disabled.  The  settings  of  MIN  (c_cc[VMIN])  and  TIME
     (c_cc[VTIME]) determine the circumstances in which a read(2) completes; there are four distinct cases:

      * MIN  == 0; TIME == 0: If data is available, read(2) returns immediately, with the lesser of the number of bytes available, or the number of bytes requested.  If no data is available, read(2)
       returns 0.

      * MIN > 0; TIME == 0: read(2) blocks until the lesser of MIN bytes or the number of bytes requested are available, and returns the lesser of these two values.

      * MIN == 0; TIME > 0: TIME specifies the limit for a timer in tenths of a second.  The timer is started when read(2) is called.  read(2) returns either when at least one byte of data is avail‐
       able, or when the timer expires.  If the timer expires without any input becoming available, read(2) returns 0.

      * MIN  >  0; TIME > 0: TIME specifies the limit for a timer in tenths of a second.  Once an initial byte of input becomes available, the timer is restarted after each further byte is received.
       read(2) returns either when the lesser of the number of bytes requested or MIN byte have been read, or when the inter-byte timeout expires.  Because the timer is only started after the  ini‐
       tial byte becomes available, at least one byte will be read.  

要するに、canonicalモードだとlineごとに読み込みますよ、ということ。
あと、noncanonicalモードだとMIN/TIMEというのが設定できて、それによって一定時間だけ入力を待つとかいうことができるよ、ということも書いてあるようだ(ただなぜかVMINやVTIMEを設定しても期待した動作をしなかった)。

サンプルコード

//! gcc -o nonblocking2.bin nonblocking2.c -W -Wall -O3 -std=gnu99 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main( void )
{
    struct termios save_settings;
    struct termios settings;

    tcgetattr( fileno( stdin ), &save_settings );
    settings = save_settings;

    settings.c_lflag       &= ~( ECHO | ICANON ); /* echobackしない & LFを待たない */
    /* settings.c_lflag       &= ~( ECHO ); /\* echobackしない *\/ */
    /* settings.c_lflag       &= ~( ICANON ); /\* LFを待たない *\/ */
    tcsetattr( fileno( stdin ), TCSANOW, &settings );
    fcntl( fileno( stdin ), F_SETFL, O_NONBLOCK ); /* non blocking */

    int c;
    while( 1 ) {
        c =getchar();

        if ( c != EOF ) {
            printf( "> %c [ %#02x ]\n", c, c );
        } else {
            if ( errno != EAGAIN ) break; /* EAGAIN: no resouce is available */
        }
        
        if ( c == 0x04 ) break; /* 0x04 == EOT(End of Transmission) : type Ctrl-D */
    }

    tcsetattr( fileno( stdin ), TCSANOW, &save_settings );
    
    return 0;
}