I am trying to read from the command line either a non negative number or a minus sign and if the input is not one of those the program terminates so I did the following:
while((scanf("%d", &n)==1 && n>-1 && (input=1)) || (scanf("%c", &delete)==1 && delete=='-' && (input=2)))
input is an integer and indicates which was read a number or the minus sign (input=1 & input=2 in the loop are intended).
Now, the program works for the numbers, but if I enter a minus sign the first scanf() reads it but doesn't put it back apparantly, so the second scanf() finds nothing and terminates the program.
However, if I enter -- (two minus signs) the first scanf() reads one and the second scanf() reads the other and it all works correctly.
The input from the command line can't be changed, it has to be one minus sign. What is the best way of solving this?
Note that I do know how to use getchar() to read the input and then check it but I don't want to do that its a hassle.
The best way is to read entire lines of text into a buffer, and then inspect the contents of the buffer, not relying on brittle chains of reading which depend on pushback.
Just use fgets() and sscanf().
Related
This question already has an answer here:
How to read / parse input in C? The FAQ
(1 answer)
Closed 2 years ago.
I am implementing a super simple menu selection procedure with a basic input validity check mechanism. The legal inputs are {1,2,3} so the possible issues would be either a number that is out of this range, or a non integer. My code is shown below. This works fine for the former issue (i.e. when i input "4") but for the latter (when i try to input a char), it prints the invalidity message over and over again rather than waiting for a new input, it's like it skips the scanf line entirely on every iteration except the first.. what am I missing here ?
do{
try = scanf("%d", &selection);
if(try!=1 || selection < 1 || selection > 3){
printf("\nInvalid input. Dear guest, please enter '1', '2', or '3'.\n\nInput:");
}
}while(try!=1 || selection < 1 || selection > 3);
After entering a character which cannot be part of the text representation of a decimal number, say 'A', the input available to the program is the byte sequence 'A' '\n' (the latter being a newline), or perhaps 'A' '\r' '\n' on Windows (carriage return followed by a newline).
When scanf tries to parse a number from these characters it balks already at the 'A' and puts it back in the input stream. Input streams in C guarantee that you can perform at least one ungetc(), that is, put at least one character back into the stream so that it will be the first character read by the next input operation. This simple but ingenious facility makes it a lot easier to process variable-format input: Imagine you parse an expression in some C source code, and both integer literal or a variable name are syntactically allowed as the next token: You can try the number first, and if that fails, the input still contains all of the variable name to process. The work of keeping the first failing character "in mind" and making it available to other parts of the program is encapsulated in the FILE implementation.
This is what happens here. The first failing scanf() puts the 'A' back so that it will be encountered again by the next attempt, ad infinitum. The 'A' must be removed from the input. More specifically, the next "word" should be removed from the input altogether: The user may have entered "kkjkllkjlk", and you don't want 10 error messages for that. You can decide whether you would accept "lklkj2" (and read the 2) but it is simpler to discard the whole word.
You can also decide whether you would accept "1 2 3 2" as 4 valid successive inputs or whether you demand newlines between the numbers; for generality (for example, if the input does not come from a terminal) I would accept all number sequences separated by whitespace of any kind. In this case you simply want to read to the next whitespace:
#include <ctype.h>
// ...
while(!isspace(getchar())) { /* ignore */ }
This should do the trick. It is possible that more whitespace is following this one, including newlines etc., but that's OK: The symbolic input conversions of scanf (like %d) skip leading whitespace.
I think it is neat to let the user exit the program by ending the input (inserting end-of-file by pressing Ctrl-z in a Windows Console, or Ctrl-d on a Posix terminal), so I would test for the special case of scanf() returning EOF. If the input is from a pipe that may actually be essential. The fringe case that a bad input is immediately followed by EOF needs a check for EOF even in the code discarding wrong input (Posix: echo -n "a" | myprog would hang; -n suppresses the usual newline which echo usually appends). Putting it all together, my take on the input loop is this:
while(1) { // break on good input
printf("Please enter your choice of 1, 2 or 3:\n");
try = scanf("%d", &selection);
if(try!=1 || selection < 1 || selection > 3){
if(try == EOF)
{
return 0;
}
printf("\nThe input was not 1,2 or 3. Please try again.\n");
int discard;
{
do{
discard = getchar();
if(discard == EOF) { return 0;} // catches EOF after bad char
}while(!isspace(discard));
}
}
else break;
}
I'm reading/practicing with this book about C language: "C Programming - A Modern Approach 2" and I stumbled upon this piece of code whose explanation is odd to me:
Since scanf doesn't normally skip white spaces, it's easy to detect the end of an input line: check to see if the character just
read is the new-line character. For example, the following loop
will read and ignore all remaining characters in the current input
line:
do {
scanf("%c", &ch);
} while (ch != '\n');
When scanf is called the next time, it will read the first character on the next input line.
I understand the functioning of the code (it exits the loop when it detects enter or '\n') but not the lesson(?).
What is the purpose of this, since to store the input from the user into &ch you HAVE to press the enter key (quitting the loop)?
Also what does "the following loop will read and ignore all remaining characters in the current input line" mean actually?
...What is the purpose of this...
For context in understanding what this code snippet can be used for, take a look at this link discussing stdio buffering.
In simplest logical terms, this technique consumes content placed in stdin until the newline character is seen. (From a user hitting the <enter> key.) This is evidently the lesson.
This effectively clears the stdin buffer of content, also described in this more robust example.
Also what does
"the following loop will read and ignore all remaining characters in
the current input line"
mean actually?
do {
scanf("%c", &ch);
} while (ch != '\n');
It means that if the new char just read by scanf is not equal to the newline character, defined as \n, it will continue to overwrite c with the next read. Once ch does equal \n, the loop will exit.
So I had a code where I use
scanf("%[^\n]s",a);
and has multiple scanf to take different inputs some being string input. So I understand that scanf("%[^\n]s",a) takes input until new line has been reached, however I was wondering suppose my string can only hold up to 10 characters, then after my string has been filled, but new line hasn't been reached how can i get rid of the extra input before going to new line. I was thinking of doing getchar() until new line has been reached however in order to even check if my 10 spots has been filled I need to use getchar, so doesn't that mess up my next scanf input? Anybody have any other way to do it? Still using scanf() and getchar?
scanf("%[^\n]s",a) is a common mistake; the %[ directive is distinct from the %s directive. What you're asking from scanf is:
A group of non-'\n' characters, followed by...
A literal s character.
Perhaps you intended to write scanf("%[^\n]",a)? Note the deleted s...
You can use the * modifier to suppress assignment for a directive, for example scanf("%10[^\n]", a); followed by scanf("%*[^\n]"); to read and discard up to the next newline and getchar(); to read and discard that newline:
scanf("%10[^\n]", a);
scanf("%*[^\n]"); // read and discard up to the next newline
getchar(); // read and discard that newline
As pointed out, the two format strings could be concatenated to reduce the number of calls to scanf. I wrote my answer this way for the sake of documentation, and I'll leave it as is. Besides, I figure that attempt at optimisation would be negligible; a profiler is likely to indicate much more significant bottlenecks for optimisation in realistic scenarios.
You can use this format to hold the first 10 characters and keep the next lines of input:
scanf("%10[^\n]%*[^\n]",a);
getchar();
According to the man page for sscanf(), the * character
An optional '*' assignment-suppression character: scanf() reads input
as directed by the conversion specification, but discards the input.
From this, I (rightly?) assume that something like
sscanf(string,"%*[^|]%*c%[^|]%*c", vars)
Would take the input, "text|neededtext|", ignore all text before the first "|" (ie deleting it from stdin?), ignore (and delete from stdin) the next character, ie the "|" store the "neededtext" and then delete the final "|" character, leaving stdin empty?
If yes, then is it ever needed to run a cleanup function after this sscanf() call, to catch some weird exception just in case something goes wrong, or is the code above always guaranteed to work?
I have run some tests, and it appears that sscanf() does eat up all characters from stdin, but I just want to make sure.
Not always. See #2
A #Jonathan Leffler commented, sscanf() works with strings and does not affect stdin. Let us assume the question is about the *scanf() family instead.
Problems with *scanf(string,"%*[^|]%*c%[^|]%*c", vars)
Code does not check the result of *scanf(). This leads to subtle to detect problems. Best to code a check.
char vars[100];
if (sscanf(string,"%*[^|]%*c%99[^|]%*c", vars) != 1) Handle_Error();
"%*[^|]" scans nothing and stops the whole scan function if the leading character is '|'. "%*[^|]" scans 1 or more non-'|', not 0 or more. Hence the "Not always" above. With scanf(), no characters are consume - they remain in stdin.
The only possible character after "%*[^|]" to continue scanning is '|'. Might as well code "%*[^|]%|" than "%*[^|]%*c"
sscanf() differs from scanf() and fscanf() in that the later 2 can scan and save a null character '\0'. sscanf() stops scanning upon reaching the null character. IMO: Use of "%s" and "%[^...] with fscanf() scanf() should be avoided to prevent a hacker exploit. OP's fgets()+sscanf() is the better approach.
%[^|] lacks a width. Without this limit, buffer overrun is not prevented. See #1.
"%*c" at the end may or may not scan a character. The result value does not reflect its success or failure as scan directives with '*' do not contribute directly to the return value. If successful trailing scanning of "%*c" was needed (which could be "|" per point #3), suggest using "%n" to detect is scanning proceed that far.
char vars[100];
int n = 0;
sscanf(string,"%*[^|]|%99[^|]| %n", vars);
if (n > 0) Success();
// May also want to use
if (string[n]) Fail_ExtraTextOnLine();
In C, %*[^|] is a scanf specifier, not delimiter.
I am using fgets() to get user input, and then I parse it into a double using sscanf(). Here is my example code:
int menuItem = 0;
char input[3];
printf("Enter valid menu item"); //values are 1, 2, 3, 4, 5, and -1
fgets(input, sizeof input, stdin);
sscanf(input, "%d", &menuItem);
The error occurs if the user enters 3 digits (invalid input, and I want to handle it accordingly), it skips my next fgets() by automatically placing the third digit as the next input. For example, if the user inters 123, it will now skip the next fgets and use the value 3 as the input. How can I clear this, or at most, only read 1 digit (but still being able to handle -1).
Why don't you just make your input array larger? Like 50 bytes... If the user enters that many, they are idiots and deserve strange behaviour =)
This is of course inelegant, but putting it in perspective, you are probably not writing industrial-strength code here. You just want to fix a silly bug, and this is the simplest solution.
[edit]
In case you are confused as to why I suggested this. Your input array is only large enough to hold 2 values, plus a null-terminator. So obviously, you want it to be large enough that you can be sure the user entered a single choice and not two choices
Your input buffer allows 1 digit, 1 newline, and 1 terminal null '\0', which really isn't big enough.
Suppose the user types 100 followed by a newline. The first call to fgets() will read 2 digits (10), then the second call will read the third digit and newline; then the next call will wait for the next input.
Simply increase your buffer size to something more sensible. For a lot of programs that I write, I use a buffer size of 4096. The POSIX standard sets _POSIX2_LINE_MAX to 2048. If you're unhappy with that, simply go with a convenient number like 64 or 128; you won't go too far wrong.
Your technique with fgets() and sscanf() is definitely the better direction to go, but you should check that sscanf() parsed the correct number of values. One of the advantages of fgets() plus sscanf() is that you can make better reports about what was erroneous in the line that the user entered; this is much less easy to do if you use fscanf() or scanf() directly.