Home > database >  Taking string input in C with spaces
Taking string input in C with spaces

Time:01-29

I was compiling my C programs in online gdb. There I faced a peculiar problem. I was trying to store 10 sentences in a 2D array, for which I wanted to take 10 inputs of string with spaces terminated by new line. I tried all possible syntax like scanf("%[^\n]%*c") or scanf("%[^\n]s") and so on; none of which worked. There after I tried to manually create a function to take input with spaces; even that didn't work. I need help as to why things aren't working. I have attached my code below and the manual way in which I attempted to take input with spaces.

int main()
{
    int c;
    printf("Enter the number of sentences\n");
    scanf("%d",&c);
    char s[c][100];
    for(int i=0; i<c; i  )
    {
        printf("Enter your sentence ");
        int k = 0;
        scanf("%c", &s[i][k]);
        while (s[i][k]!='\n')
        {
            k  ;
            scanf("%c", &s[i][k]);
        }
        s[i][k]='\0';
    }
}

CodePudding user response:

If you flush the \n that scanf() leaves behind then you can use fgets() to read each sentence:

#include <stdio.h>
#define SENTENCE_LEN 100

int main() {
    printf("Enter the number of sentences\n");
    int c;
    if(scanf("%d",&c) != 1) {
        // handle error
        return 1;
    }
    for(;;) {
        int ch = getchar();
        if(ch == EOF) return 1;
        if(ch == '\n') break;
    }
    char s[c][SENTENCE_LEN];
    for(size_t i = 0; i < c; i  ) {
        printf("Enter your sentence ");
        if(!fgets(s[i], SENTENCE_LEN, stdin)) {
            // handle error
            return 1;
        }
        // strip trailing newline
        s[i][strcspn(s[i], "\n")] = '\0';
    }
    for(size_t i = 0; i < c; i  ) {
        printf("%s\n", s[i]);
    }
}

and example session:

Enter the number of sentences
2
Enter your sentence hello world
Enter your sentence hello friend
hello world
hello friend

CodePudding user response:

The line

scanf("%[^\n]s", ... )

will not work, as %[...] and %s are two distinct conversion format specifiers. You appear to be attempting to use a hybrid of both, which will probably not work. The s in the format string will be interpreted as a literal s, so that scanf will expect to see a literal s in the input stream and consume it. This is not what you want.

Conversely, the line

scanf("%[^\n]%*c", ... )

should work (unless the input is an empty line), despite your claim that it doesn't work. Since you have not provided a minimal reproducible example of that code, I cannot tell you why it doesn't work with you.

Regarding your posted code, the problem is described in the following question:

scanf() leaves the newline character in the buffer

The problem is that the line

scanf("%d",&c);

will not consume an entire line. It will at least leave the newline character of the line on the input stream. Therefore, the first call to

scanf("%c", &s[i][k]);

will probably read the leftover newline character, instead of the first character of the sentence.

One simple solution is to call the function getchar beforehand, so that the newline character is consumed from the input stream. However, this will only work if the newline character is the only leftover character on the input stream. If it is possible that there are more leftover characters on the input stream, then there are several ways of consuming all of them:

Using scanf:

scanf( "%*[^\n]" );
getchar();

Using getchar in a do...while loop:

int c;

do
{
    c = getchar();

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

Using getchar in a compact for loop:

for ( int c; ( c = getchar() ) != EOF && c != '\n'; )
    ;

After fixing all of the issues mentioned above and adding code to output the results, your program should look like this:

#include <stdio.h>

int main( void )
{
    int c;

    //get the number of sentences from the user
    printf( "Enter the number of sentences: " );
    scanf( "%d", &c );

    //discard all leftover characters
    scanf( "%*[^\n]" );
    getchar();

    //declare variable-length array based on user input
    char s[c][100];

    //get the input sentences
    for ( int i=0; i<c; i   )
    {
        printf( "Enter your sentence: " );
        int k = 0;
        scanf( "%c", &s[i][k] );

        while ( s[i][k] != '\n' )
        {
            k  ;
            scanf( "%c", &s[i][k] );
        }
        s[i][k] = '\0';
    }

    //print the sentences
    printf( "\nThe results are:\n\n" );
    for ( int i=0; i<c; i   )
    {
        printf( "%s\n", s[i] );
    }
}

This program has the following behavior:

Enter the number of sentences: 5
Enter your sentence: test1
Enter your sentence: test2
Enter your sentence: test3
Enter your sentence: test4
Enter your sentence: test5

The results are:

test1
test2
test3
test4
test5

Note that this program is only guaranteed to work with lines up to 99 bytes, otherwise you will have a buffer overflow, which means that your program may crash. Also, if the number the user enters at the start of the program is invalid (for example a negative number or not a number at all), then the program may also crash or misbehave in some other way.

When dealing with line-based user input, using scanf to partially read a line is generally not recommended. It is generally better to always read an entire line of input at once, for example using fgets. I have therefore rewritten your entire program to not use scanf at all, but to use fgets instead:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

//forward declarations
int get_int_from_user( const char prompt[] );
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );

int main( void )
{
    int c;

    //get the number of sentences from the user
    for (;;)
    {
        c = get_int_from_user( "Enter the number of sentences: " );

        //break out of loop if input is valid
        if ( c > 1 )
            break;

        printf( "Input must be a positive integer, please try again!\n" );
    }

    //declare variable-length array based on user input
    char s[c][100];

    //get the input sentences
    for ( int i=0; i<c; i   )
    {
        get_line_from_user( "Enter your sentence: ", s[i], sizeof s[i] );
    }

    //print the sentences
    printf( "\nThe results are:\n\n" );
    for ( int i=0; i<c; i   )
    {
        printf( "%s\n", s[i] );
    }
}

//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char prompt[] )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

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

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

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

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfj23jlj" gets rejected
        for ( ; *p != '\0'; p   )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

//This function will read exactly one line of input from the
//user. If the line is too long to fit in the buffer, then the
//function will automatically reprompt the user for input. On
//failure, the function will never return, but will print an
//error message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
    for (;;)
    {
        char *p;

        //prompt user for input
        fputs( prompt, stdout );

        //attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to find newline character
        p = strchr( buffer, '\n' );

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            //a missing newline character is ok if the next
            //character is a newline character or if we have
            //reached end-of-file (for example if the input is
            //being piped from a file or if the user enters
            //end-of-file in the terminal itself)
            if ( !feof(stdin) && (c=getchar()) != '\n' )
            {
                printf( "Input was too long to fit in buffer!\n" );

                //discard remainder of line
                do
                {
                    if ( c == EOF )
                    {
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                    c = getchar();

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

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

        //input was ok, so break out of loop
        break;
    }
}

I have taken the function get_int_from_user from this answer of mine to another question. Please see that answer for more information on how that function works. For example, I have designed the function in such a way that it performs full input validation and automatically reprompts the user if the input is invalid.

This second program has the same behavior as the first program, except that the input validation is now robust and the program won't crash if the user enters invalid input:

Enter the number of sentences: -5
Input must be a positive integer, please try again!
Enter the number of sentences: 0
Input must be a positive integer, please try again!
Enter the number of sentences: test
Error converting string to number!
Enter the number of sentences: 6abc
Unexpected input encountered!
Enter the number of sentences: 6
Enter your sentence: test1
Enter your sentence: test2
Enter your sentence: test3
Enter your sentence: test4
Enter your sentence: test5
Enter your sentence: test6

The results are:

test1
test2
test3
test4
test5
test6
  • Related