Search code examples
c++consolegetchar

Detect Ctrl+d if input buffer is not empty using getchar()


I'm writing a shell-like interpreter using getchar() for buffered input.

  • If Enter is pressed, the interpreter should process the buffer, then prompt for a new line.
  • If Ctrl+d is pressed, the interpreter should process the buffer, then exit.

The code below runs, but does not fully satisfy the second requirement.

#include <iostream>

using namespace std;

void shell() {
    for (int input;;) {
        // prompt at beginning of line
        std::cout << ">>> "; 
        while ((input = getchar()) != '\n') { 
            // still in current line
            if (input == EOF) {
                // ctrl+d pressed at beginning of line
                return;
            } 
            std::cout << "[" << (char)input << "]";
        }
        // reached end of line
    }
}

int main() {
    cout << "before shell" << endl;
    shell();
    cout << "shell has exited" << endl;
    return 0;
}

My issue is getchar() only returns EOF when the buffer is empty. Pressing Ctrl+d mid-line causes getchar() to return each buffered character except the EOF character itself.

How can I determine if Ctrl+d is pressed mid-line?

I've considered using a timeout. In this method, the interpreter assumes Ctrl+d was pressed if getchar() halts for too long after returning something other than a line feed. This is not my favorite approach because it requires threading, introduces latency, and the appropriate waiting period is unclear.


Solution

  • With a normal line there's a '\n' at the end. Not so with a line pushed with Ctrl+D in Unix-land shell. So, for example,

    #include <stdio.h>
    #include <unistd.h>     // read
    
    void shell()
    {
        char line[256];
        for( bool finished = false; not finished; )
        {
            printf( ">>> " );
            //fgets( line, sizeof( line ), stdin );
    
            fflush( stdout );
            const int n_bytes = read( 0, line, sizeof( line ) - 1 );
            line[n_bytes] = '\0';
    
            char const* p = line;
            finished = true;
            while( char const input = *p++ )
            { 
                if( input == '\n' )
                {
                    finished = false;
                    break;
                } 
                printf( "[%c]", input );
            }
            printf( "\n" );
        }
    }
    
    auto main()
        -> int
    {
        printf( "before shell\n" );
        shell();
        printf( "shell has exited\n" );
    }
    

    Leaving for you to fix the following:

    • Handle EOF (empty line pushed).
    • Rewrite in terms of C++ iostreams, not C FILE* i/o.
    • With a Ctrl+D-pushed input line there's a missing newline in the console output.

    Note 1: read is generally available also with Windows compilers. However,

    Note 2: Ctrl+D for pushing the current line is a Unix-land convention. If you want your program to exhibit that behavior regardless of how it's run or on what system, you'll have to use some portable low level character input library such as ncurses.