Here's the basic skeleton of my code:
int getSentence(SearchResults* input){
printf("Enter sentence:");
fgets(input->sentence, 100, stdin);
printf("Enter message ID:");
fgets(input->messageID, 20, stdin);
return 1;
}
As is, this has a couple of problems. One is that when I print the string later, they have a newline character at the end, which I don't want. The other is that if I enter more than the 100 or 20 characters, it will overflow those characters into the next fgets call.
I've found "solutions" online, but they all either introduce new problems, or are recommended against using. Most of what I found is summarized in this SO post: How to properly flush stdin in fgets loop.
fflush works for the second problem, but is apparently "problematic".
I came up with *(strstr(input->sentence, "\n")) = '\0'; for the first issue, but it doesn't help with the second
while ((getchar()) != '\n')(and variations) is a commonly recommended solution for both problems, but it introduces a new issue: my next input requires two enters before the program will continue running. I tried adding fputc('\n', stdin); after the while loop and it solves all three problems, but only when the input exceeds the character limit. If I write a sentence less than 100 chars, I have to hit enter twice for the program to continue.
setbuf(stdin, NULL) is one of the solutions given, but one of the comments says "mucking around with setbuf is even worse [than fflush]"
I recommend that you create your own function that calls fgets, verifies that the entire line was read in and removes the newline character. If the function fails due to the input being too long, you can print an error message, discard the remainder of the line and reprompt the user. Here is a function that I have written myself for this purpose some time ago:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. 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 (;;) //infinite loop, equivalent to while(1)
{
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' );
//reprompt user for input by restarting loop
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
Here is a demonstration program in which I apply my function to your code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//forward declaration
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );
int main( void )
{
char sentence[100];
char messageID[20];
get_line_from_user( "Enter sentence: ", sentence, sizeof sentence );
get_line_from_user( "Enter message ID: ", messageID, sizeof messageID );
printf(
"\n"
"The following input has been successfully read:\n"
"sentence: %s\n"
"message ID: %s\n",
sentence, messageID
);
}
//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. 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 (;;) //infinite loop, equivalent to while(1)
{
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' );
//reprompt user for input by restarting loop
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
This program has the following behavior:
Enter sentence: This is a test sentence.
Enter message ID: This is another test sentence that is longer than 20 characters and therefore too long.
Input was too long to fit in buffer!
Enter message ID: This is shorter.
The following input has been successfully read:
sentence: This is a test sentence.
message ID: This is shorter.
Here's what I'm using to solve my issue, thanks to Steve Summit and Andreas Wenzel for their comments.
int getSentence(SearchResults* input){
printf("Enter sentence:");
fgets(input->sentence, 100, stdin);
int temp = strcspn(input->sentence, "\n");
if(temp < 100 - 1) input->sentence[temp] = '\0';
else while ((temp = getchar()) != '\n' && temp != EOF);
printf("Enter message ID:");
fgets(input->messageID, 20, stdin);
temp = strcspn(input->messageID, "\n");
if(temp < 20 - 1) input->messageID[temp] = '\0';
else while ((temp = getchar()) != '\n' && temp != EOF);
return 1;
}
EDIT: For anyone else who has the same issue as me, see comments below for additional problems you may need to account for if you use this solution, such as fgets returning NULL.
Related
Good afternoon, my question is conceptual. How can I make it generate a "fancy" error when the user incorrectly enters some data that does not correspond to the scanf() function? So as to only allow integers to be entered in the example below (not characters or array of characters or an inappropriate data).
For example:
#include <stdio.h>
int a;
printf("Enter a number\n");
scanf("%d", &a); //the user is supposed to enter a number
printf("Your number is %d ", a);
//but if the user enters something inappropriate, like a character, the program leads to
//undetermined behavior (which as I understand it interprets said character according to its
//value in the ASCII code).
From already thank you very much
In order to determine whether scanf was able to successfully convert the input to an integer, you should check the return value of scanf:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int num;
printf( "Enter a number: " );
if ( scanf( "%d", &num ) != 1 )
{
printf( "Failed to convert input!\n" );
exit( EXIT_FAILURE );
}
printf( "Conversion successful! The number is %d.\n", num );
}
However, using scanf for line-based user input is generally not recommended, because scanf does not behave in an intuitive manner when dealing with that kind of input. For example, scanf will generally not consume an entire line of input at once. Instead, it will generally only consume the input that matches the argument, but will leave the rest of the line on the input stream, including the newline character.
Leaving the newline character on the input stream can already cause a lot of trouble. For example, see this question.
Also, if the user enters for example 6abc, then scanf will successfully match the 6 and report success, but leave abc on the input stream, so that the next call to scanf will probably immediately fail.
For this reason, it is generally better to always read one line of input at a time, using the function fgets. After successfully reading one line of input as a string, you can use the function strtol to attempt to convert the string to an integer:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
char line[200], *p;
int num;
//prompt user for input
printf( "Enter a number: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Input failure!\n" );
exit( EXIT_FAILURE );
}
//attempt to convert string to integer
num = strtol( line, &p, 10 );
if ( p == line )
{
printf( "Unable to convert to integer!\n" );
exit( EXIT_FAILURE );
}
//print result
printf( "Conversion successful! The number is %d.\n", num );
}
However, this code has the following issues:
It does not check whether the input line was too long to fit into the buffer.
It does not check whether the converted number is representable as an int, for example whether the number is too large to be stored in an int.
It will accept 6abc as valid input for the number 6. This is not as bad as scanf, because scanf will leave abc on the input stream, whereas fgets will not. However, it would probably still be better to reject the input instead of accepting it.
All of these issues can be solved by doing the following:
Issue #1 can be solved by checking
whether the input buffer contains a newline character, or
whether end-of-file has been reached, which can be treated as equivalent to a newline character, because it also indicates the end of the line.
Issue #2 can be solved by checking whether the function strtol set errno to the value of the macro constant ERANGE, to determine whether the converted value is representable as a long. In order to determine whether this value is also representable as an int, the value returned by strtol should be compared against INT_MIN and INT_MAX.
Issue #3 can be solved by checking all remaining characters on the line. Since strtol accepts leading whitespace characters, it would probably also be appropriate to accept trailing whitespace characters. However, if the input contains any other trailing characters, the input should probably be rejected.
Here is an improved version of the code, which solves all of the issues mentioned above and also puts everything into a function named get_int_from_user. This function will automatically reprompt the user for input, until the input is valid.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
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 "6abc" 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;
}
}
int main( void )
{
int number;
number = get_int_from_user( "Enter a number: " );
printf( "Input was valid.\n" );
printf( "The number is: %d\n", number );
return 0;
}
This program has the following behavior:
Enter a number: abc
Error converting string to number!
Enter a number: 6000000000
Number out of range error!
Enter a number: 6 7 8
Unexpected input encountered!
Enter a number: 6abc
Unexpected input encountered!
Enter a number: 6
Input was valid.
The number is: 6
How to get verified user input of a specific type
#1 Get user input as a string
char s[100];
if (!fgets( s, sizeof(s), stdin )) *s = '\0';
char * p = strptok( s, "\r\n" );
if (!p) complain_and_quit();
*p = '\0';
...
Alternately:
#define __STDC_WANT_LIB_EXT2__ 1
#include <stdio.h>
char * s = NULL;
size_t n = 0;
if (getline( &s, &n, stdin ) < 0)
{
free( s );
complain_and_quit();
}
...
free( s );
#2 Get rid of any trailing whitespace
This could easily be put in a trim() function, but here we’ll spell it out:
Can’t believe I forgot this step. Sorry. 😳
p = strchr( s, '\0' );
while (p-- != s) if (!isspace( *p )) break;
p[1] = '\0';
#3 Try to convert that string to the type of thing you want.
char * p;
int user_input = strtol( s, &p, 10 );
if (*p)
{
// Input was not JUST an integer.
// It could be something like "123 xyz", or "not-an-integer".
// Look at the value of p to figure out where the conversion went wrong.
complain();
}
do_something_with_an_integer( user_input );
That’s it!
I am very new to the C programming language and I am trying to make a login thing, the problem that I am having is that I can't get the user_len to compare in my while statement.
Thank you for any and all feedback
EDIT:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct User_name{
int user_id;
char username[20];
char password[30];
} User_data;
//make all the variable
int i;
int user_len;
int pass_len;
//prototype function
char user ();
int user_name();
int main() {
//get username
user_name (User_data.username);
}
int user_name() {
while(user_len > 20) {
printf("Enter your username:\n");
scanf("%s",User_data.username);
user_len = strlen(User_data.username);
if (user_len > 20) {
printf("\nusername is too long please enter again:%d\n",user_len);
} else {
printf("Username is: %s\n",User_data.username);
}
}
return(user_len);
}
The content of the while loop will never be executed, because the condition in the line
while(user_len > 20) {
will be false at the start of the program, because user_len will have the value 0.
Also, the lines
char user ();
int user_name();
are not prototype declarations. They are forward declarations of functions that take an unspecified number of arguments. If you want to declare that they take no arguments, then you should change them to:
char user( void );
int user_name( void );
See the following question for more information:
Warning/error "function declaration isn't a prototype"
The following code has a serious problem:
printf("Enter your username:\n");
scanf("%s",User_data.username);
user_len = strlen(User_data.username);
if (user_len > 20) {
printf("\nusername is too long please enter again:%d\n",user_len);
} else {
printf("Username is: %s\n",User_data.username);
}
There is no point in checking for a buffer overflow after the buffer overflow has already occurred. The buffer overflow must be prevented in the first place, in order to prevent your program from invoking undefined behavior (i.e. to prevent your program from possibly crashing).
The easiest way to prevent the buffer overflow would be to change the scanf format string from "%s" to "%19s", which would limit the number of matched characters to 19, so that it will not write more than 20 characters to User_data.username (including the terminating null character).
However, this solution is not ideal, as it is possible that scanf will leave non-whitespace characters from that line on the input stream (this is also possible when using "%s"). These non-whitespace characters will likely cause trouble in the next loop iteration when scanf is called.
For this reason, it would probably be best if you used the function fgets instead of scanf. The function fgets has the advantage that it will always read exactly one line at a time (provided that the supplied input buffer is large enough to store the whole line), which is not necessarily the case with scanf.
Here is my solution to the problem, which uses fgets instead of scanf:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct User_name
{
int user_id;
char username[20];
char password[30];
} User_data;
//make all the variable
int i;
int user_len;
int pass_len;
//prototype function
int user_name( void );
int main( void )
{
//get username
user_name();
}
int user_name( void )
{
while( true )
{
char line[100];
char *p;
//prompt user for input
printf( "Enter your username: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Error reading input!\n" );
exit( EXIT_FAILURE );
}
//find newline character
p = strchr( line, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( p == NULL )
{
//a missing newline character is ok on end-of-file condition
if ( !feof( stdin ) )
{
int c;
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Error reading from input\n" );
return false;
}
} while ( c != '\n' );
goto line_too_long;
}
}
else
{
//remove newline character by overwriting it with null character
*p = '\0';
}
//find length of string
user_len = strlen( line );
//check if length is acceptable
if ( user_len >= 20 )
{
goto line_too_long;
}
//username is ok, so copy it
strcpy( User_data.username, line );
//break out of infinite loop
break;
line_too_long:
printf( "Line was too long, try again!\n" );
}
//print username
printf( "Username is: %s\n", User_data.username );
return user_len;
}
This program has the following behavior:
Enter your username: ThisIsAVeryLongUserName
Line was too long, try again!
Enter your username: ShortUserName
Username is: ShortUserName
Note that this program uses one goto label and two goto statements. Normally it is a good idea to try to not use goto, but to use normal control flow statements instead. However, in this case, I believe that using goto was appropriate. The alternative would have been to duplicate the line printf( "Line was too long, try again!\n" ); and use a continue statement in two places in the program. I believe that it is better to handle this error in one place in the program (though I understand that this topic is highly controversial and that some people would consider it better to use code duplication).
Another issue worth mentioning is that you are using global variables (which I have taken over in my solution). This is considered bad programming style and should generally be avoided. Here is a modified version of my solution which avoids the use of global variables:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct user_data
{
int user_id;
char username[20];
char password[30];
};
//prototype function
int user_name( void );
int main( void )
{
//get username
user_name();
}
int user_name( void )
{
struct user_data ud;
int user_len;
while( true )
{
char line[100];
char *p;
//prompt user for input
printf( "Enter your username: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Error reading input!\n" );
exit( EXIT_FAILURE );
}
//find newline character
p = strchr( line, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( p == NULL )
{
//a missing newline character is ok on end-of-file condition
if ( !feof( stdin ) )
{
int c;
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Error reading from input\n" );
return false;
}
} while ( c != '\n' );
goto line_too_long;
}
}
else
{
//remove newline character by overwriting it with null character
*p = '\0';
}
//find length of string
user_len = strlen( line );
//check if length is acceptable
if ( user_len >= 20 )
{
goto line_too_long;
}
//username is ok, so copy it
strcpy( ud.username, line );
//break out of infinite loop
break;
line_too_long:
printf( "Line was too long, try again!\n" );
}
//print username
printf( "Username is: %s\n", ud.username );
return user_len;
}
I've been coding a program to write data into a text file and practice data processes in c, and find data from there, every data is stored as lines. There are lines, and data is stored line by line, such as:
student name student surname student phone etc.
When i take an input of "student name" it starts to print without printing the name itself, prints what comes after it, same happens if i search for surname, only phone will be printed out.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE *filePtr;
filePtr=fopen("std.txt","r");
char char_input[50];
char string[500];
printf("%s","Please give an input of the phone number\n");
scanf("%s",char_input);
while(!feof(filePtr)){
fscanf(filePtr,"%s",string);
if(strcmp(string, char_input)== 0){
fgets(string,500,filePtr);
puts(string);
}
}
fclose(filePtr);
}
Text file:
Andrew Brooks 865 965 55
Input:
Andrew
Output:
Brooks 865 965 55
Desired output:
Andrew Brooks 865 965 55
Instead of incorrectly using feof() and fscanf(filePtr,"%s", ... to incorrectly read a line. Use fgets() to read a line of the file and convert to a string.
Test the return value of fgets() to see if input occurred.
Use strstr() to look for a matching sub-string within string.
Example:
while (fgets(string, sizeof string, filePtr)) {
if (strstr(string, char_input)){
fputs(string, stdout);
}
}
The function feof will only tell you whether a previous input operation has already encountered end-of-file. It won't tell you whether you have now reached the end of file, so that the next input operation will fail. That function function is unable to predict whether the next input operation to fscanf or fgets will fail. Therefore, it should generally not be used as a loop condition. See this question for further information: Why is “while ( !feof (file) )” always wrong?
In your case, feof may return false and the subsequent function call to fscanf may return EOF due to encountering end-of-file. In that case, your posted code will ignore the return value of fscanf and behave as if fscanf had succeeded, and your posted code will attempt to process the non-existant input. This is likely to result in a bug.
Therefore, instead of using the function feof to determine whether the loop should be continued, you should check the return value of the input function.
You could rewrite your loop like this:
while ( fscanf(filePtr,"%s",string) == 1 ) {
if ( strcmp(string, char_input ) == 0 ) {
fgets( string, 500, filePtr );
puts( string );
}
}
This will solve the problem mentioned above of not checking the return value of fscanf. However, depending on the exact input, it may also be possible that the function fgets will fail due to encountering end-of-file. Therefore, it would be better if your program also checked the return value of the function fgets, instead of simply assuming that the function succeeded.
Another problem is that the line
puts(string);
will only print the contents of string, which is " Brooks 865 965 55". However, you also want to print "Andrew", which was read by the fscanf function call but has been meanwhile overwritten by the fgets function call. The simplest solution would be to print it before it gets overwritten. However, this will not work if the user searches for "Brooks" instead of "Andrew", because the word "Andrew" will already have been discarded in the previous loop iteration. This is because calling fscanf(filePtr,"%s",string) in a loop will not read one line of input per loop iteration, but will instead read a single word per loop iteration (which is not very meaningful).
Another consequence of reading in the input file word by word using fscanf(filePtr,"%s",string) is that your won't be able to find a match for the phone number "865 965 55". This is because your program will first read "865" from the input file and determine that this "word" is not identical to the search string. It will then read "965" and determine the same thing. It will do the same for "55".
The best solution would probably be to redesign your loop so that it always reads exactly one line of input per loop iteration, instead of only one word per loop iteration. After reading in one line of input, you can then parse the line by splitting it into "first name", "last name" and "phone number" using the function sscanf.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *filePtr;
char search_string[50];
char line[200];
//open input file
filePtr = fopen( "std.txt", "r" );
if ( filePtr == NULL )
{
fprintf( stderr, "unable to open input file!\n" );
exit( EXIT_FAILURE );
}
//prompt user for input
printf( "Please enter search string: " );
//Note that the following code now uses "fgets" instead
//of "fscanf", because fscanf will only read a single
//word, when using the "%s" format specifier. This means
//that it would be unable to read the phone number
//"865 965 55" as an input string, because that line
//consists of three "words".
//read exactly one line of input from user
if ( fgets( search_string, sizeof search_string, stdin ) == NULL )
{
fprintf( stderr, "input failure!\n" );
exit( EXIT_FAILURE );
}
//remove newline character from input line by
//replacing it with terminating null character
search_string[strcspn(search_string,"\n")] = '\0';
//read exactly one line of input from the input file
//per loop iteration
while ( fgets( line, sizeof line, filePtr ) != NULL )
{
char first_name[50];
char last_name[50];
char phone_number[50];
//attempt to parse input
if (
sscanf(
line,
"%49s %49s %49[^\n]",
first_name,
last_name,
phone_number
)
!= 3
)
{
fprintf(
stderr,
"WARNING: skipping line due to parse error!\n"
);
continue;
}
//parsing was successful, so we can now search the
//3 individual fields for the search string
if (
strcmp( search_string, first_name ) == 0
||
strcmp( search_string, last_name ) == 0
||
strcmp( search_string, phone_number ) == 0
)
{
//remove newline character from input line by
//replacing it with terminating null character
line[strcspn(line,"\n")] = '\0';
//print entire input line of file for user
printf( "%s\n", line );
}
}
//cleanup
fclose(filePtr);
}
This program has the following behavior:
Please enter search string: Andrew
Andrew Brooks 865 965 55
Please enter search string: Brooks
Andrew Brooks 865 965 55
Please enter search string: 865 965 55
Andrew Brooks 865 965 55
Note that the code above is not perfect, as it has the following issues:
When using fgets, if the input line is too long to fit in the buffer, then the program will not detect this, although it should probably print an error message and quit, in such a situation.
If any of the fields "first name", "last name" or "phone number" is larger than 49 characters, the code does prevent a buffer overflow (which would possibly cause your program to crash), but it still doesn't handle this situation properly, for example by checking for such a situation and by printing an appropriate error message.
However, for your purposes, the code should probably be sufficient.
A more robust program, which fixes these issues, would be the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
//This function will read exactly one line of input using
//fgets and verify that the line was not too long for the
//input buffer. Note that the buffer size must be two bytes
//longer than the actual string length, because there must
//be space for the newline character and the terminating
//null character. The newline character will be overwritten
//with another terminating null character.
//On success, it will return true. If not further input is
//available due to end-of-file, it will return false.
//Otherwise, the function will not return, but will
//terminate the program with an error message.
bool get_one_line_of_user_input( char *buffer, int buffer_size )
{
char *p;
if ( fgets( buffer, buffer_size, stdin ) == NULL )
{
if ( feof( stdin ) )
{
return false;
}
else
{
fprintf( stderr, "input error!\n" );
exit( EXIT_FAILURE );
}
}
p = strchr( buffer, '\n' );
if ( p == NULL )
{
//No newline character was found. This could mean
//that the line was too long to store in the input
//buffer, in which case, the program should quit
//with an error message. However, it could also mean
//that input has been redirected to come from a
//file, and that this file ends with a line without
//a line ending. In that case, the missing newline
//character can be ignored.
if ( !feof( stdin ) )
{
fprintf( stderr, "line too long for buffer!\n" );
exit( EXIT_FAILURE );
}
}
else
{
//remove newline character
*p = '\0';
}
return true;
}
int main()
{
FILE *filePtr;
char search_string[50];
char line[200];
//open input file
filePtr = fopen( "std.txt", "r" );
if ( filePtr == NULL )
{
fprintf( stderr, "unable to open input file!\n" );
exit( EXIT_FAILURE );
}
//prompt user for input
printf( "Please enter search string: " );
//read exactly one line of input from user
if ( !get_one_line_of_user_input( search_string, sizeof search_string ) )
{
fprintf( stderr, "input failure!\n" );
exit( EXIT_FAILURE );
}
//read exactly one line of input from the input file
//per loop iteration
while ( get_one_line_of_user_input( line, sizeof line ) )
{
char first_name[50];
char last_name[50];
char phone_number[50];
//attempt to parse input
if (
sscanf(
line,
"%49s %49s %49[^\n]",
first_name,
last_name,
phone_number
)
!= 3
)
{
fprintf(
stderr,
"WARNING: skipping line due to parse error!\n"
);
continue;
}
//verify that none of the fields was too long
if (
strlen( first_name ) == 49
||
strlen( last_name ) == 49
||
strlen( phone_number ) == 49
)
{
//At least one buffer is full, and we have no way
//to determine whether the limit was exceeded or whether
//we are merely at the limit, so we must assume that
//the limit was exceeded.
fprintf(
stderr,
"WARNING: skipping line due to field length "
"limit exceeded!\n"
);
continue;
}
//parsing was successful, so we can now search the
//3 individual fields for the search string
if (
strcmp( search_string, first_name ) == 0
||
strcmp( search_string, last_name ) == 0
||
strcmp( search_string, phone_number ) == 0
)
{
//print entire input line of file for user
printf( "%s\n", line );
}
}
//cleanup
fclose(filePtr);
}
I'm trying to write to a program that takes in an integer from the terminal and safely checks if it actually is an int and if it is equal to or in between 0 and 5. The code should also have retry logic.
This is the code that I have written:
#include <stdio.h>
#include <stdlib.h>
int main(){
int input;
char i[3];
char *p;
while(fgets(i, (sizeof(i)),stdin)){
input=strtol(i,&p,10);
if(input<0 || input>5 || p==i || (*p)!='\n'){
printf("please enter a integer larger or equal to 0 or smaller or equal to 5\n");
}
else{
printf("%d",input);
break;
}
}
}
The problem I have encountered is that if I input more characters than fgets() reads such as "aaaaa", I get the output:
please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5
I'm assuming the issue is that the characters that fgets does not read get left in the buffer and get read on subsequent iterations which trigger the if statement and therefore the repeated output. I'm assuming the solution is the somehow clear the buffer but from what I've read fflush() isn't allowed. Is there a simple way to clear the buffer? And how would you go about doing it, if that actually is the issue to the problem?
A simplistic approach starts with a bigger buffer.
#define BUF_N 100
char buf[BUF_N];
Then a not so simple approach that tests for all sort of troubles. I suspect simplifications exists, but at least this one handles many corners and is illustrative.
For simplicity, if the input line is excessively long, let us just say the input line is bad, regardless of text read.
#define BUF_N 100
char buf[BUF_N];
while (fgets(buf, sizeof buf, stdin)) {
// If entire buffer filled, the line may be longer, read it.
if (strlen(buf) == BUF_N - 1 && buf[BUF_N - 2] != '\n') {
printf("Excessive input\n");
// Consume rest of the line
int ch;
while ((ch = fgetc(stdin)) != '\n') {
if (ch == EOF) {
return EOF;
}
}
continue;
}
char *p;
long input = strtol(buf, &p, 10); // use long
if (buf == p) {
printf("Non-numeric input\n");
continue;
}
if (input < 0 || input > 5) {
printf("Out of range %ld\n", input);
continue;
}
// Look for trailing junk
while (isspace(*(unsigned char* )p)) {
p++;
}
if (*p) {
printf("Trailing junk\n");
continue;
}
return (int) input;
}
return EOF;
}
The above is not so tolerant of reading null characters.
Calling the function fgets with a buffer size of 3 will read at most one character, in addition to the newline character and the terminating null character. If the input is supposed to be a number between 0 and 5, then this behavior is acceptable, but you should be aware that your program will be unable to read input such as 05, even though it fulfills the range requirement.
If you want to allow input such as 05, then you should increase the buffer size.
You can easily detect whether the user entered more characters into the line than fit into the buffer. Since the function fgets also reads and stores the newline character into the buffer, you can check for the existance of this newline character, for example using the function strchr. If you determine that fgets did not read a newline character, then this means that the input was too long to fit into the buffer. In that case, you probably want to reject the input and discard the remainder of the input, so that it does not get read by the next call to fgets. This is what was happening in your program, which caused the same line to be processed multiple times.
One way of discarding the remainder of the input is to call getchar, which reads one character at a time. You should repeat this until you encounter the newline character, which means that the end of the line has been reached.
This is how the code could look:
while( fgets( i, sizeof i, stdin ) != NULL )
{
//verify that entire line of input was read
if ( strchr( i, '\n' ) == NULL )
{
int c;
//verify that stream state is as expected
if ( feof(stdin) || ferror(stdin) )
{
fprintf( stderr, "unexpected input error!\n" );
exit( EXIT_FAILURE );
}
//print error message
printf( "Line input was too long to fit into buffer!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "unexpected input error!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//input was successful, so we can now process it with strtol
}
Note that you must additionally #include <string.h> in order to use the function strchr.
void openMenu(int *op) {//edited
do {
printf("Your turn...\t\n");
scanf(" %d", op);
if (*op > 14 || *op < 1 ) {
printf("Only enter a number between 1 and 14!\n");
}
} while (*op > 14 || *op < 1 );
}
I am trying to make a checking loop which controls if the value which was entered is between 1 and 14. If there are letters it has to repeat too, the enter process.
Perhaps it isn't working and everytime it goes in the second run the scanf didn't run.
I checked the thing to set a space in front of the %d, but it isn't working too...
Did u have maybe a nice idea?
Working with Xcode on Mac 11.1
You need to check the returning value of your scanf:
#include <stdio.h>
void openMenu(int *op) {//edited
do {
printf("Your turn...\t\n");
if (scanf(" %d", op) != 1 || *op > 14 || *op < 1 ) {
while(getchar()!='\n'); // clean the input buffer
printf("Only enter a number between 1 and 14!\n");
}
} while (*op > 14 || *op < 1 );
}
int main()
{
int op;
openMenu(&op);
printf("Number Read {%d}\n", op);
return 0;
}
A more robust (and complicated) solution would be the following:
int isNumber(char buffer[]){
for(int i = 0; buffer[i] != '\0'; i++)
if(!isdigit((unsigned char) buffer[i]))
return 0;
return 1;
}
int readInput(char buffer[]){
int result = scanf("%99s", buffer);
while(getchar()!='\n');
return result;
}
int isInRange(int *op, char buffer[]){
*op = atoi(buffer);
return *op <= 14 && *op >= 1;
}
void openMenu(int *op) {
do {
char buffer[100];
if(readInput(buffer) && isNumber(buffer) && isInRange(op, buffer)) {
break;
}
printf("Only enter a number between 1 and 14!\n");
} while (1);
}
This would avoid that input such as 4odgjlda would be considered a valid number. Nevertheless, with the current approach inputs such as 4 odgjlda would still be considered as a valid input, since scanf would read the first word and not the entire line. For a more robust solution you should use fgets instead. One can see an example of such solution on the answer provided by Andreas Wenzel.
The problem is that if you enter something like "sdfokhs" the first time, then scanf will not be able to match any integer and will return 0. Because scanf did not consume this invalid input from the input stream, calling scanf a second time won't cause the user to be prompted for new input. Instead, scanf will attempt again to match an integer from the non-consumed input, and will fail again for the same reason as the fist time. This means you have an infinite loop.
Therefore, to fix this, you must consume the rest of the line before calling scanf again, for example like this:
while ( fgetc( stdin ) != '\n' ) ;
Or, if you want more robust error checking:
int c;
do
{
c = fgetc( stdin );
if ( c == EOF )
{
printf( "Unrecoverable error reading input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
Also, it is always a good idea to check the return value of scanf.
However, in this case, I don't recommend using scanf. It would make more sense to always read exactly one line of input per loop iteration, using fgets. The disadvantage of using scanf is that it may read several lines of input per iteration, or only part of one line, which requires you to consume the rest of the line.
The following solution is longer than the solution of all other answers, but it is also the one with the most robust input validation and error handling.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINESIZE 100
void openMenu(int *op)
{
char buffer[MAX_LINESIZE];
char *p;
long converted;
//goto label
try_again:
//prompt user for input
printf( "Please enter number between 1 and 14: " );
//read line of input into buffer
if ( fgets( buffer, MAX_LINESIZE, stdin ) == NULL )
{
printf( "Unrecoverable error reading input!\n" );
exit( EXIT_FAILURE );
}
//make sure that a full line was read and remember position of newline character
p = strchr( buffer, '\n' );
if ( p == NULL )
{
int c;
printf( "Input was too long!\n" );
//attempt to consume input until newline character found
do
{
c = fgetc( stdin );
if ( c == EOF )
{
printf( "Unrecoverable error reading input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
goto try_again;
}
//remove newline character from string
*p = '\0';
//convert string to number
converted = strtol( buffer, &p, 10 );
//make sure conversion was successful
if ( p == buffer )
{
printf( "Only enter a number!\n" );
goto try_again;
}
//verify that remainder of line is whitespace
while ( *p != '\0' )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Only enter a number!\n" );
goto try_again;
}
p++;
}
//verify that number was in the correct range
if ( converted < 1 || converted > 14 )
{
printf( "Only enter a number between 1 and 14!\n" );
goto try_again;
}
//since all tests were passed, write the value
*op = converted;
}
Note that using goto should normally not be done, if a loop can be used just as well. However, in this case, I believe it is the cleanest solution.
as i can see you are comparing the *op (which i assume is a pointer).
so, check if you have already assigned the value to the predefined variable or not.
It somewhat should look like this.
int value = 0;
int *op = &value;
do {
printf("Your turn...\t\n");
scanf(" %d", op);
if (*op > 14 || *op < 1 ) {
printf("Only enter a number between 1 and 14!\n");
}
}while (*op > 14 || *op < 1 );