Home > Enterprise >  Is there a C equivalent of Python's input() function for safe, robust keyboard input?
Is there a C equivalent of Python's input() function for safe, robust keyboard input?

Time:03-15

One problem I have writing console apps for real-world users in C is that I don't know how of a way to get keyboard input from the user in a safe, robust, simple way. For example, this program has several issues:

#include<stdio.h>
void main()
{
  char name[8];
  printf("Enter Your Name : ");
  scanf("%s",&name);
  printf("Your name is : %s\n", name);
  return 0;
}
  • The program can't handle blank input.
  • The program drops text after a space.
  • The program goes beyond the array size and is unsafe.

I'd like to have a safe, robust (i.e. doesn't crash on unexpected input), simple function that can get a string from the user via keyboard input. I'm looking for something that works similar to Python's input() function. Does this exist in the C standard library, or is there a function I can copy/paste into my program? I'd prefer to not have to deal with loading another library.

Ideally, the function would include the following features:

  • The user types some text and presses Enter.
  • The function call blocks until the user presses Enter.
  • The left/right arrow keys move the cursor, and backspace & delete work normally.
  • The user can enter any amount of text. (Any excess text can be truncated, or some other sensible default behavior.)

The scanf() function isn't good enough. I need a function I can call that gets keyboard input and returns a string; it would just work.

ADDITIONAL NOTE: The fgets() function is also not what I'm looking for. If a user enters more text than is allocated for the buffer, then the remainder of the text is automatically used the next time fgets() is called.

CodePudding user response:

First off, if you do not need ANSI C getline(NULL, 0, stdin) is the best solution.

Ok, new try to answer this right. I know this problem. 1.) First you need line input but getc violates your constraint to scroll back. It can only put one char back. So you need a function of the get family to do this. It is blocking and requires Enter as always on command line. 2.) Next you want to use any text length. But that is the problem in C as there are no Strings with variable lenght. The OS defines a maximal length (see Bash command line and input limit). So you can use this size as buffer and it will be save. Otherwise you could only take one char or block (see fread) after another which would violate your first constraint. (see 1.). At least (if you don't want to use POSIX Calls) there might be a hack: Depending on what machine (x86 or other) you are using, you can pass either the Heap- or the Stack-Pointer. It might overwrite your process memory and it will crash then. But if this is the case, there was no chance to do it right with stdin either. If your machine has not enough RAM you can only read blockwise and put the data into a file (e.g. on hard drive). You may want to use ungetc() to check if Enter was pressed or you might need some other file operations (see stdio.h). Don't forget that C was designed for UNIX, so "everything is a file" and you can use file operations on stdin (which is your console input).

Because of the overflow problem gets (stdio.h) was removed from the POSIX library: https://en.wikipedia.org/wiki/C_POSIX_library.

CodePudding user response:

The easiest way in ISO C to read a whole line of input is to use the function fgets.

The user can enter any amount of text. (Any excess text can be truncated, or some other sensible default behavior.)

That is where is gets complicated. The easiest solution would simply report failure if it detects that the line is too long to fit in the buffer:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

//This function will return true on success, false on failure.
//The buffer must be two bytes larger than the actual length of
//the line. It needs an extra byte for the newline character
//(which will be removed) and for the terminating null
//character.
bool get_line_from_user( char *buffer, int buffer_size )
{
    char *p;

    if ( fgets( buffer, buffer_size, stdin ) == NULL )
    {
        fprintf( stderr, "Error reading from input\n" );
        return false;
    }

    //make sure that entire line was read in (i.e. that
    //the buffer was not too small)
    if ( ( p = strchr( buffer, '\n' ) ) == NULL )
    {
        //a missing newline character is ok on end-of-file condition
        if ( !feof( stdin ) )
        {
            int c;

            fprintf( stderr, "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Error reading from input\n" );
                    return false;
                }

            } while ( c != '\n' );

            return false;
        }
    }
    else
    {
        //remove newline character by overwriting it with null character
        *p = '\0';
    }

    return true;
}

int main( void )
{
    char line[20];

    printf( "Please enter a line of input:\n" );

    if ( get_line_from_user( line, sizeof line ) )
    {
        printf( "Input was valid. The input was:\n%s", line );
    }
    else
    {
        printf( "Input was invalid." );
    }
}

This program has the following behavior:

Valid input:

Please enter a line of input:
This is a test.
Input was valid. The input was:
This is a test.

Input too long:

Please enter a line of input:
This is a test line that is too long to fit into the input buffer.
Line input was too long!
Input was invalid.

Of course, a buffer size of only 20 is obviously too small for most common tasks. I only set it this low in order to demonstrate the error message. Normally, I would set the buffer size to, maybe, 200.

Under most circumstances, it should be no problem to increase the buffer size to several kilobytes if necessary, even if the buffer is being allocated on the stack.

If you want to be able to read more than a few kilobytes in a single line (for example if the input is being redirected from a file), then it would probably be better to allocate a sufficiently sized array that is on the heap instead of the stack, by using the function malloc.

If you want the maximum line size to be truly unlimited, then you could call the function fgets multiple times per line, until it encounters the newline character, and resize the buffer as necessary. See this question for further information (credit goes to the comments section for this link).

ADDITIONAL NOTE: The fgets() function is also not what I'm looking for. If a user enters more text than is allocated for the buffer, then the remainder of the text is automatically used the next time fgets() is called.

That is why my program discards the remainder of the line from the input stream, if it detects that the line was too long. That way, you do not have this problem.

  • Related