gcc 4.4.2
I was reading an article about scanf. I personally have never checked the return code of a scanf.
#include <stdio.h>
int main(void)
{
char buf[64];
if(1 == scanf("%63s", buf))
{
printf("Hello %s\n", buf);
}
else
{
fprintf(stderr, "Input error.\n");
}
return 0;
}
I am just wondering what other techniques experienced programmers do when they use scanf when they want to get user input? Or do they use another function or write their own?
Thanks for any suggestions,
EDIT =========
#include <stdio.h>
int main(void)
{
char input_buf[64] = {0};
char data[64] = {0};
printf("Enter something: ");
while( fgets(input_buf, sizeof(input_buf), stdin) == NULL )
{
/* parse the input entered */
sscanf(input_buf, "%s", data);
}
printf("Input [ %s ]\n", data);
return 0;
}
I think most programmers agree that scanf is bad, and most agree to use fgets and sscanf. However, I can use fgets to readin the input. However, if I don't know what the user will enter how do I know what to parse. For example, like if the user was to enter their address which would contain numbers and characters and in any order?
Don't use scanf directly. It's surprisingly hard to use. It's better to read an entire line of input and to then parse it (possibly with sscanf).
Read this entry (and the entries it references) from the comp.lang.c FAQ:
http://c-faq.com/stdio/scanfprobs.html
Edit:
Okay, to address your additional question from your own edit: If you allow unstructured input, then you're going to have to attempt to parse the string in multiple ways until you find one that works. If you can't find a valid match, then you should reject the input and prompt the user again, probably explaining what format you want the input to be in.
For anything more complicated, you'd probably be better off using a regular expression library or even using dedicated lexer/parser toolkits (e.g. flex and bison).
I don't use scanf() for interactive user input; I read everything as text using fgets(), then parse the input as necessary, using strtol() and strtod() to convert text to numeric values.
One example of where scanf() falls down is when the user enters a bad numeric value, but the initial part of it is valid, something like the following:
if (scanf("%d", &num) == 1)
{
// process num
}
else
{
// handle error
}
If the user types in "12e4", scanf() will successfully convert and assign the "12" to num, leaving "e4" in the input stream to foul up a future read. The entire input should be treated as bogus, but scanf() can't catch that kind of error. OTOH, if I do something like:
if (fgets(buffer, sizeof buffer, stdin))
{
int val;
char *chk;
val = (int) strtol(buffer, &chk, 10);
if (!isspace(*chk) && *chk != 0)
{
// non-numeric character in input; reject it completely
}
else
{
// process val
}
}
I can catch the error in the input and reject it before using any part of it. This also does a better job of not leaving garbage in the input stream.
scanf() is a great tool if you can guarantee your input is always well-formed.
scanf() has problems, in that if a user is expected to type an integer, and types a string instead, often the program bombs. This can be overcome by reading all input as a string (use getchar()), and then converting the string to the correct data type.
/* example one, to read a word at a time */
#include <stdio.h>
#include <ctype.h>
#define MAXBUFFERSIZE 80
void cleartoendofline( void ); /* ANSI function prototype */
void cleartoendofline( void )
{
char ch;
ch = getchar();
while( ch != '\n' )
ch = getchar();
}
main()
{
char ch; /* handles user input */
char buffer[MAXBUFFERSIZE]; /* sufficient to handle one line */
int char_count; /* number of characters read for this line */
int exit_flag = 0;
int valid_choice;
while( exit_flag == 0 ) {
printf("Enter a line of text (<80 chars)\n");
ch = getchar();
char_count = 0;
while( (ch != '\n') && (char_count < MAXBUFFERSIZE)) {
buffer[char_count++] = ch;
ch = getchar();
}
buffer[char_count] = 0x00; /* null terminate buffer */
printf("\nThe line you entered was:\n");
printf("%s\n", buffer);
valid_choice = 0;
while( valid_choice == 0 ) {
printf("Continue (Y/N)?\n");
scanf(" %c", &ch );
ch = toupper( ch );
if((ch == 'Y') || (ch == 'N') )
valid_choice = 1;
else
printf("\007Error: Invalid choice\n");
cleartoendofline();
}
if( ch == 'N' ) exit_flag = 1;
}
}
I make a loop call fgets until the end of the line is read, and then call sscanf to parse the data. It's a good idea to check whether sscanf reaches the end of the input line.
I rarely use scanf. Most of the times, I use fgets() to read data as a string. Then, depending upon the need, I may use sscanf(), or other functions such as strto* family of functions, str*chr(), etc., to get data from the string.
If I use scanf() or fgets() + sscanf(), I always check the return values of the functions to make sure they did what I wanted them to do. I also don't use strtok() to tokenize strings, because I think the interface of strtok() is broken.
Related
How to accept set of strings as input in C and prompt the user again to re-enter the string if it exceeds certain length. I tried as below
#include<stdio.h>
int main()
{
char arr[10][25]; //maximum 10 strings can be taken as input of max length 25
for(int i=0;i<10;i=i+1)
{
printf("Enter string %d:",i+1);
fgets(arr[i],25,stdin);
}
}
But here fgets accepts the strings greater than that length too.
If the user hits return, the second string must be taken as input. I'm new to C
How to accept string input only if it of certain length
Form a helper function to handle the various edge cases.
Use fgets(), then drop the potential '\n' (which fgets() retains) and detect long inputs.
Some untested code to give OP an idea:
#include <assert.h>
#include <stdio.h>
// Pass in the max string _size_.
// Return NULL on end-of-file without input.
// Return NULL on input error.
// Otherwise return the buffer pointer.
char* getsizedline(size_t sz, char *buf, const char *reprompt) {
assert(sz > 0 && sz <= INT_MAX && buf != NULL); // #1
while (fgets(buf, (int) sz, stdin)) {
size_t len = strlen(buf);
// Lop off potential \n
if (len > 0 && buf[--len] == '\n') { // #2
buf[len] = '\0';
return buf;
}
// OK if next ends the line
int ch = fgetc(stdin);
if (ch == '\n' || feof(stdin)) { // #3
return buf;
}
// Consume rest of line;
while (ch != '\n' && ch != EOF) { // #4
ch = fgetc(stdin);
}
if (ch == EOF) { // #5
return NULL;
}
if (reprompt) {
fputs(reprompt, stdout);
}
}
return NULL;
}
Uncommon: reading null characters remains a TBD issue.
Details for OP who is a learner.
Some tests for sane input parameters. A size of zero does not allow for any input saved as a null character terminated string. Buffers could be larger than INT_MAX, but fgets() cannot directly handle that. Code could be amended to handle 0 and huge buffers, yet leave that for another day.
fgets() does not always read a '\n'. The buffer might get full first or the last line before end-of-file might lack a '\n'. Uncommonly a null character might be read - even the first character hence the len > 0 test, rendering strlen() insufficient to determine length of characters read. Code would need significant changes to accommodate determining the size if null character input needs detailed support.
If the prior fgets() filled its buffer and the next read character attempt resulted in an end-of-file or '\n', this test is true and is OK, so return success.
If the prior fgetc() resulted in an input error, this loops exits immediately. Otherwise, we need to consume the rest of the line looking for a '\n' or EOF (which might be due to an end-of-file or input error.)
If EOF returned (due to an end-of-file or input error), no reason to continue. Return NULL.
Usage
// fgets(arr[i],25,stdin);
if (getsizedline(arr[i], sizeof(arr[i]), "Too long, try again.\n") == NULL) {
break;
}
This code uses a buffer slightly larger than the required max length. If a text line and the newline can't be read into the buffer, it reads the rest of the line and discards it. If it can, it again discards if too long (or too short).
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define INPUTS 10
#define STRMAX 25
int main(void) {
char arr[INPUTS][STRMAX+1];
char buf[STRMAX+4];
for(int i = 0; i < INPUTS; i++) {
bool success = false;
while(!success) {
printf("Enter string %d: ", i + 1);
if(fgets(buf, sizeof buf, stdin) == NULL) {
exit(1); // or sth better
}
size_t index = strcspn(buf, "\n");
if(buf[index] == '\0') { // no newline found
// keep reading until end of line
while(fgets(buf, sizeof buf, stdin) != NULL) {
if(strchr(buf, '\n') != NULL) {
break;
}
}
if(feof(stdin)) {
exit(1); // or sth better
}
continue;
}
if(index < 1 || index > STRMAX) {
continue; // string is empty or too long
}
buf[index] = '\0'; // truncate newline
strcpy(arr[i], buf); // keep this OK string
success = true;
}
}
printf("Results:\n");
for(int i = 0; i < INPUTS; i++) {
printf("%s\n", arr[i]);
}
return 0;
}
The nice thing about fgets() is that it will place the line-terminating newline character ('\n') in the input buffer. All you have to do is look for it. If it is there, you got an entire line of input. If not, there is more to read.
The strategy then, is:
fgets( s, size_of_s, stdin );
char * p = strpbrk( s, "\r\n" );
if (p)
{
// end of line was found.
*p = '\0';
return s; (the complete line of input)
}
If p is NULL, then there is more work to do. Since you wish to simply ignore lines that are too long, that is the same as throwing away input. Do so with a simple loop:
int c;
do c = getchar(); while ((c != EOF) && (c != '\n'));
Streams are typically buffered behind the scenes, either by the C Library or by the OS (or both), but even if they aren’t this is not that much of an overhead. (Use a profiler before playing “I’m an optimizing compiler”. Don’t assume bad things about the C Library.)
Once you have tossed everything you didn’t want (to EOL), make sure your input isn’t at EOF and loop to ask the user to try again.
Putting it all together
char * prompt( const char * message, char * s, size_t n )
{
while (!feof( stdin ))
{
// Ask for input
printf( "%s", message );
fflush( stdout ); // This line _may_ be necessary.
// Attempt to get an entire line of input
if (!fgets( s, n, stdin )) break;
char * p = strpbrk( s, "\r\n" );
// Success: return that line (sans newline character(s)) to the user
if (p)
{
*p = '\0';
return s;
}
// Failure: discard the remainder of the line before trying again
int c;
do c = getchar(); while ((c != EOF) && (c != '\n'));
}
// If we get this far it is because we have
// reached EOF or some other input error occurred.
return NULL;
}
Now you can use this utility function easily enough:
char user_name[20]; // artificially small
if (!prompt( "What is your name (maximum 19 characters)? ", user_name, sizeof(user_name) ))
{
complain_and_quit();
// ...because input is dead in a way you likely cannot fix.
// Feel free to check ferror(stdin) and feof(stdin) for more info.
}
This little prompt function is just an example of the kinds of helper utility functions you can write. You can do things like have an additional prompt for when the user does not obey you:
What is your name? John Jacob Jingleheimer Schmidt
Alas, I am limited to 19 characters. Please try again:
What is your name? John Schmidt
Hello John Schmidt.
As per the C FAQ: http://c-faq.com/stdio/scanfprobs.html
We should not use scanf for interactive input output, but instead we should resort to reading the whole line with fgets and then try to parse it with sscanf, prompting the user to type input again if sscanf returns parsing errors.
This, IIUC, would lead to code like this:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int main()
{
char inpbuff[5];
signed char n;
bool read_correctly = false;
while(!read_correctly) {
printf("Please enter an 8bit number: ");
if(fgets(inpbuff, sizeof(inpbuff), stdin) == NULL)
return EXIT_FAILURE;
if(sscanf(inpbuff, "%hhd", &n) == 1)
read_correctly = true;
}
printf("You have entered: %hhd\n", n);
return EXIT_SUCCESS;
}
For me this approach creates problems if the user types a line that is longer than the size of the buffer provided for fgets. Even in the program above problems start to occur if the user types in asdf or asdf14.
In such a case we should, ideally, ignore all characters until we see a '\n', ignore this \n and only then again ask the user to provide their input. This would lead to an approach like this:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int main()
{
char inpbuff[5];
signed char n;
bool read_correctly = false;
while(!read_correctly) {
printf("Please enter an 8bit number: ");
switch(scanf("%hhd", &n)) {
case 1:
read_correctly = true;
break;
case 0: {
char sink;
do {
sink = fgetc(stdin);
if(sink == EOF)
return EXIT_FAILURE;
} while(sink != '\n');
break;
}
default:
return EXIT_FAILURE;
}
}
printf("You have entered: %hhd\n", n);
return EXIT_SUCCESS;
}
Which I suppose must be suboptimal since it is contrary to what the C FAQ recommends! And I definitely do not consider myself wiser than the C FAQ authors.
So, how does a typical processing of interactive input/output in C look like?
Your version misses a corner case - suppose I type in 1r4. Your scanf call will successfully convert and assign 1 to n, return 1 indicating success, and leave r4 in the input stream to foul up the next read. Ideally you'd like to reject 1r4 altogether.
That's why it's recommended to read the input as text, then process that buffer. If someone types in a line longer than the buffer is sized for, you handle it at the input stage by checking for a newline in the buffer - if it isn't there, reject the input as too large, then read and discard any additional characters until you see the newline.
while ( fgets( buffer, sizeof buffer, stdin ) )
{
char *newline = strchr( buffer, '\n' );
if ( !newline )
{
/**
* input too large for buffer, throw away current input, read and
* discard additional characters until we see the newline or EOF
*/
for ( int c = getchar(); c != EOF && c != '\n'; c = getchar() )
;
}
else
{
// process input buffer
}
}
Yes, on a scale of 1 to pain-in-the-ass, interactive input in C defines pain-in-the-ass. You really do have to jump through all these hoops to guard against bad input.
You can bulletproof calls to scanf up to a point, but in the end it's honestly less of a hassle to do your own parsing.
This program essentially asks for a secret string, then asks a user to repeatedly guess single chars of that string until he guesses it all. It works however every second time the while loop is run it skips user input for the guessed char. How do I fix this?
int main(){
char guess;
char test2 [50];
char * s = test2;
char output [50];
char * t = output;
printf("Enter the secret string:\n");
fgets(test2, 50, stdin);
for (int i=0;i<49;i++){ //fills ouput with _ spaces
*(output +i)='_';
while(strcmp(s,t) != 0){
printf("Enter a guess:");
scanf("%c",&guess);
printf("You entered: %c\n", guess);
showGuess(guess,s, t ); // makes a string "output" with guesses in it
printf("%s\n",t);
}
printf("Well Done!");
}
For a quick and dirty solution try
// the space in the format string consumes optional spaces, tabs, enters
if (scanf(" %c", &guess) != 1) /* error */;
For a better solution redo your code to use fgets() and then parse the input.
As pointed out in some other answers and comments, you need to "consume" the "newline character" in the input.
The reason for that is that the input from your keyboard to the program is buffered by your shell, and so, the program won't see anything until you actually tell your shell to "pass the content of its buffer to the program". At this point, the program will be able to read the data contained in the previous buffer, e.g. your input, followed by one the character(s) used to validate your input in the shell: the newline. If you don't "consume" the newline before you do another scanf, that second scanf will read the newline character, resulting in the "skipped scanf" you've witnessed. To consume the extra character(s) from the input, the best way is to read them and discard what you read (what the code below does, notice the
while(getc(stdin) != '\n');
line after your scanf. What this line does is: "while the character read from stdin is not '\n', do nothing and loop.").
As an alternative, you could tell your shell to not buffer the input, via the termios(3) functions, or you could use either of the curses/ncurses libraries for the I/O.
So here is what you want:
int main(){
char guess;
char test2 [50];
char * s = test2; // 3. Useless
char output [50];
char * t = output; // 3. Useless
int i; // 8. i shall be declared here.
printf("Enter the secret string:\n");
fgets(test2, 50, stdin);
for (i=0;i<50;i++) if (test2[i] == '\n') test2[i] = '\0'; // 4. Remove the newline char and terminate the string where the newline char is.
for (int i=0;i<49;i++){ // 5. You should use memset here; 8. You should not declare 'i' here.
*(output +i)='_';
} // 1. Either you close the block here, or you don't open one for just one line.
output[49] = '\0'; // 6. You need to terminate your output string.
while(strcmp(s,t) != 0){ // 7. That will never work in the current state.
printf("Enter a guess:");
scanf("%c",&guess);
while(getc(stdin) != '\n');
printf("You entered: %c\n", guess);
showGuess(guess,s, t );
printf("%s\n",t);
}
printf("Well Done!");
return 0; // 2. int main requires that.
}
Other comments on your code:
You opened a block after your for loop and never closed it. That might be causing problems.
You declared your main as a function returning an integer... So you should at least return 0; at the end.
You seem to have understood that char * t = output; copies output's value and uses t as a name for the new copy. This is wrong. You are indeed copying something, but you only copy the address (a.k.a reference) of output in t. As a result, output and t refer to the same data, and if you modify output, t will get modified; and vice versa. Otherwise said, those t and s variables are useless in the current state.
You also need to remove the newline character from your input in the test2 buffer. I have added a line after the fgets for that.
Instead of setting all the bytes of an array "by hand", please consider using the memset function instead.
You need to actually terminate the output string after you "fill" it, so you should allocate a '\0' in last position.
You will never be able to compare the test2 string with the output one, since the output one is filled with underscores, when your test2 is NULL terminated after its meaningful content.
While variables at the loop scope are valid according to C99 and C11, they are not standard in ANSI C; and it is usually better to not declare any variable in a loop.
Also, "_ spaces" are called "underscores" ;)
Here is a code that does what you want:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN 50
int main()
{
char phrase[LEN];
char guessed[LEN];
char guess;
int i, tries = 0;
puts("Please enter the secret string:");
if(fgets(phrase, LEN, stdin) == NULL)
return 1;
for(i = 0; i < LEN && phrase[i] != '\n'; i++); // Detect the end of input data.
for(; i < LEN; i++) // For the rest of the input data,
phrase[i] = '_'; // fill with underscores (so it can be compared with 'guessed' in the while loop).
phrase[LEN - 1] = '\0'; // NULL terminate 'phrase'
memset(guessed, '_', LEN); // Fill 'guessed' with underscores.
guessed[LEN - 1] = '\0'; // NULL terminate 'guessed'
while(strcmp(phrase, guessed) != 0) // While 'phrase' and 'guessed' differ
{
puts("Enter a guess (one character only):");
if(scanf("%c", &guess) != 1)
{
puts("Error while parsing stdin.");
continue;
}
if(guess == '\n')
{
puts("Invalid input.");
continue;
}
while(getc(stdin) != '\n'); // "Eat" the extra remaining characters in the input.
printf("You entered: %c\n", guess);
for(i = 0; i < LEN; i++) // For the total size,
if(phrase[i] == guess) // if guess is found in 'phrase'
guessed[i] = guess; // set the same letters in 'guessed'
printf("Guessed so far: %s\n", guessed);
tries++;
}
printf("Well played! (%d tries)\n", tries);
return 0;
}
Feel free to ask questions in the comments, if you are not getting something. :)
Newline character entered in the previous iteration is being read by scanf. You can take in the '\n' by using the getc() as follows:
scanf("%c",&guess);
getc(stdin);
..
This changed worked for me. Though the right explanation and c leaner code is the one given by #7heo.tk
Change
scanf("%c",&guess);
with
scanf(" %c",&guess);
It should ignore '\n'.
so in this database I need to have certain limitations like the Name only letters and so.
but if I use the scanf("%s, c.name) for e.g., it only reads up until to the first space so if
I had to add 2 names like Marie Claire, I cannot use scanf.
I have a method which checks whether the string contains a digit or not (will provide it) but I was wondering if I could make the fgets() accept only the letters. I'll provide what I tried as well, but when I used this method, it's not accepting any input just skips it.
printf ("\nPlease enter Name:\n");
while (fgets (c.name, sizeof (c.name), stdin) && cCheck(c.name,100) == FALSE);
{
}
the method to check for digits [boolean is made with typedef from my side]
boolean cCheck(char *test, int max)
{
int x;
for (x =0; x<max; x++)
{
if (isdigit(test[x]))
{
return FALSE;
}
if (x==max)
{
return TRUE;
}
x++;
}
return TRUE;
}
Like the code sample below, you can specify fscanf to read only characters and also a . and ' ' (blank space) if you specify the format specifier on what are the acceptable characters to read. If you put a ^ on the front like [^a-zA-Z. ], then it will read everything but those characters.
#include <stdio.h>
int main()
{
char s[25];
printf("enter a string: ");
scanf("%25[a-zA-Z. ]c\n", s); // reads upto 25 chars of a-z/A-Z and '.', ' '.
printf("s1: %s\n", s);
return 0;
}
Example output from the above code:
c:\my-src\test-programs>scanf-test.exe
enter a string: dasdasd asdasda 34534536
s1: dasdasd asdasda
c:\my-src\test-programs>scanf-test.exe
enter a string: werfdsfsd3423524525
s1: werfdsfsd
See the following links for more details:
fscanf
INFO: scanf() Format Specifications and Syntax
but if I use the scanf("%s, c.name) for e.g., it only reads up until
to the first space so if I had to add 2 names like Marie Claire, I
cannot use scanf.
you can write
scanf( "%s %s", c.name, c.surname ); // better with s_scanf
I have a method which checks whether the string contains a digit or
not (will provide it) but I was wondering if I could make the fgets()
accept only the letters.
No, fgets reads characters up to and including \n there is nothing you can do about that. Instead use that to your advantage by reading the characters into a buffer and extracting the names e.g. with sscanf() or even strtok()
again recommending use of _s versions of functions to avoid nasty surprises.
In short no. But it's nearly always better to separate the input and the checking - so use fgets to read the text, and then check it afterwards with isalpha or something similar.
Did you try to remove the '\n' from the string.
#include <ctype.h>
#include <stdio.h>
char buf[SIZE];
int good;
do {
size_t i;
char *pend;
good = 1;
fgets(buf, sizeof buf, stdin);
pend = strchr(buf, '\n');
if (pend != NULL)
*pend = '\0';
else
flush_stdin();
for (i = 0; buf[i] != '\0' && good; i++)
if (!isdigit(buf[i])
good = 0;
} while (good != 1);
How I fixed it:
// ----- Name Input ----- //
printf ("\nPlease enter Name:\n");
char nameCheck[50];
fgets (nameCheck,sizeof (nameCheck),stdin);
while (cCheck(nameCheck,50) == FALSE)
{
fgets(nameCheck,sizeof (nameCheck),stdin);
}
strcpy (c.name, nameCheck);
I added a new char nameCheck[50];
did an fgets() on it
did a while with the nameCheck and then did a strcpy :)
Here's a basic question:
How can I take the input from a user and only accept numbers?
I know I need to use this start:
do{
ch=getchar();
}while (ch != '\n');
But I know it's not enough. This will block every input, including numbers, so I need to break when input is number.
also how do I break not after the first digit of the number?
I tried looking this up with no luck.
thanks!
When you need to perform error checking, do different things based on the input, etc., it's best to read user input line by line and process each line as you see fit.
// Make it large enough for your needs.
#define LINE_LENGTH 200
char line[LINE_LENGTH];
// Keep reding lines of text until there is nothing to read.
while ( fgets(line, sizeof(line), stdin) != NULL )
{
// Process contents of line.
}
If you expect to see only one number per line, you can use sscanf to extract numbers from each line.
while ( fgets(line, sizeof(line), stdin) != NULL )
{
int num;
if ( sscanf(line, "%d", &num) == 1 )
{
// Got a number. Use it.
}
}
The probably easiest way to read in a number is using scanf:
int number;
if (scanf("%d",&number) == 1) {
printf("successfully read number %d\n",number);
} else {
printf("not a number.\n");
}
It skips leading white spaces and then takes characters from stdin as long as these match an integral number format. Note that you still might have to press "enter" before your program will proceed, because the operating system might buffer the input (beyond your control).
Note that a ch=getchar() will take also the first non-digit value from stdin, which can then not be consumed by any further access to stdin anymore. scanf, in contrast, keeps this character in the buffer for later use.
Just check if the input is some value between '0' and '9'
#include <stdio.h>
int main(void)
{
size_t len = 0;
char str[32];
int ch;
while (len < sizeof(str) - 1) {
ch = getchar();
if ((ch >= '0') && (ch <= '9')) {
str[len++] = (char)ch;
} else {
break;
}
}
str[len] = '\0';
puts(str);
return 0;
}
simple, nice and neat solution:), I used to solve similar questions when I started programming, this will let you only input numbers
#include <stdio.h>
int main()
{
char ch;
do{
ch=getchar();
}while (!(ch>=48 && ch<=57));
printf("yay,we got a number : %c !",ch);
return 0;
}