0

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.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
Al Sweigart
  • 11,566
  • 10
  • 64
  • 92
  • 4
    The correct function for reading a line of input from the user is fgets – stark Mar 14 '22 at 20:33
  • What about the input functions in the cs50 library https://cs50.readthedocs.io/libraries/cs50/c/ https://github.com/cs50/libcs50 They are used so that people learning to program don't need to know how to handle input. – Jerry Jeremiah Mar 14 '22 at 20:34
  • 4
    For safe input, you want either Unix/Posix `getline`, or writing same yourself for better portability. – hyde Mar 14 '22 at 20:36
  • And if you are writing software which has no problem with GPL, check out https://en.m.wikipedia.org/wiki/GNU_Readline – hyde Mar 14 '22 at 20:39
  • Use fgets with stdin. Here is an example: https://stackoverflow.com/questions/3919009/how-to-read-from-stdin-with-fgets – Miguel Mar 14 '22 at 20:41
  • Use fgets with stdin as filepointer: https://stackoverflow.com/questions/3919009/how-to-read-from-stdin-with-fgets. – Miguel Mar 14 '22 at 20:42
  • Using plain `fgets` makes handling long lines a pain. Either the code doesn't work right with too long lines, or it becomes complex with all the error checking. You want to wrap it in your own function, which does something with long lines. – hyde Mar 14 '22 at 20:47
  • Well, I'm not saying it's an *equivalent* of Python's `input` but, in the case given, you could use a `scanf` format specifier like `%7[^\n]`, as I recently explained [here](https://stackoverflow.com/a/71400806/10871073). And you could even use that format in an `sscanf` call, after reading an input line with `fgets`. – Adrian Mole Mar 14 '22 at 21:04
  • fgets() isn't what I'm looking for, since if the user enters too much text, the remainder is automatically entered the next time fgets() is called. – Al Sweigart Mar 14 '22 at 22:40
  • @AlSweigart: It is possible to discard the remainder of the line, if the line is too long. This is what I have done in my answer. – Andreas Wenzel Mar 14 '22 at 22:57

2 Answers2

2

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.

Miguel
  • 41
  • 4
  • 2
    [`getline()` is still part of the POSIX standard](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/getline.html). And none of C++ with `std::getline()` nor Java's `BufferedReader.readLine()` (nor Python nor...) have any more of a problem with dynamically allocating long lines than C's `getline()` - the "`getline()` can overflow!" argument is weak - at best. – Andrew Henle Mar 14 '22 at 21:49
  • But `getline()` *does* require you to deal with freeing dynamically-allocated memory, and it cannot safely be used to read into an existing object that is not dynamically allocated or must not be reallocated. – John Bollinger Mar 14 '22 at 21:59
  • The newest way is getline(NULL, 0, stdin), it allocates the memory for you. You have to free it as usual. It might not work with some (older) microcontrollers. It was added 2008 to POSIX because of the problems, so it is the best method for sure. – Miguel Mar 14 '22 at 22:13
  • 2
    The OS does not set a limit to string length; the only limit is available memory. You are misreading the post you link about shell maximum command length, which is about the maximum length of a shell command. (Although not all shell commands are limited, either.) If you read on, you'll see that the answer explicitly says "Note that the input to a program (as read on stdin or any other file descriptor) is not limited (only by available program resources)." – rici Mar 14 '22 at 23:11
  • I do need an ANSI C function, or a code made from C code, not C++. – Al Sweigart Mar 15 '22 at 01:25
  • @AlSweigart: the Posix `getline` function is C, not C++. And it's what you want, probably. (Although `readline` is what I normally use for console apps.) – rici Mar 15 '22 at 03:56
  • getline would be the right answer, if ANSI C is not a precondition. Without an OS (see unistd.h) either it is difficult but may be there is one and you could use read on stdin afterwards (after have waiten for Enter). Without OS you have other possibilities like Interrupts. The main problems stays, that you do not know how many chars will be entered. getline() would resize the array itself but other functions don't. So you have to allocate the max available memory (difference between stack and heap pointer) or JUST wait for enter somehow, then get the required size, allocate the mem and read. – Miguel Mar 15 '22 at 23:36
0

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.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • The second time i call `get_line_from_user( line, sizeof line )`, it seems to prepopulate the command line with the previous entered input. Adding a call to `fflush(stdin);` doesn't seem to stop this. Is there a workaround for this issue? – Al Sweigart Mar 14 '22 at 22:46
  • @AlSweigart: A character array, such as `line`, is only able to store a single string. If you want to be able to store two strings, then you could, for example, allocate two character arrays, for example like this: `char line1[200], line2[200];.` However, this would be wasteful of space. It would probably be better to copy the string using [`strdup`](https://en.cppreference.com/w/c/string/byte/strdup), before calling `get_line_from_user` a second time (which will overwrite `line`). – Andreas Wenzel Mar 14 '22 at 22:57
  • Ah, my mistake. The second time it was called it was fine. – Al Sweigart Mar 15 '22 at 01:21