Scenario:
I am trying to validate some user-input.
In my case the user is only allowed to
enter between 0 and 3 lowercase characters,
optionally including whitespace.
In Regex: ^[a-z ]{0,3}$
If the user enters more than 3 characters or
the input string contains invalid values,
in each case a different return-value and
error message has to be printed.
What I tried is to read the input into a temporary
char array and defining the scanset as 4[a-z ],
so that only the correct characters will be read
and one char more, in order to check if the maximal
number of desired characters has been read.
I.e. if the last element in this temporary array
is not empty the user input was bigger than 3.
Problem:
When the user enters 3 correct chars
and a 4th wrong char, the 4th won't be read,
therefore we read 3 valid chars,
we "allegedly" never read an invalid char and
the length of read chars is also valid,
all tough it of course is not!
Code:
//--------------------------------------
int scan_input(char* char_array)
{
int status = 0;
int max_size = 3;
char temp_array[max_size+1];
// Print system prompt:
printf("plain text: ");
// Read user input:
status = scanf("%4[a-z ]", temp_array);
if (temp_array[max_size] != '\0')
{
printf("[ERR] too many characters\n");
return -1;
}
if (status != 1)
{
printf("[ERR] invalid characters\n");
return -2;
}
strcpy(char_array,temp_array);
printf("[OK] Input is valid!\n");
return 0;
}
Output:
$ gcc -Wall -std=c11 application.c && ./a.out
plain text: abcD
[OK] Input is valid!
I am grateful for every hint to fix this blind spot!
PS.:
If you know a better approach to solve this problem, than by doing it with scanf() and the scanset, your thoughts are welcome!
to validate some user-input
Separate the input from the parsing
Use a wide buffer and fgets().
char buf[80];
if (fgets(buf, sizeof buf, stdin)) {
// we have some input
Then parse and use "%n", which records the scan position, to test success.
int max_size = 3;
char temp_array[max_size+1];
int n = 0;
temp_array[0] = '\0';
sscanf(buf, "%3[a-z ]%n", temp_array, &n);
bool success = buf[n] == '\n' || buf[n] == '\0';
If sscanf() did not scan anything, n == 0 and the prior temp_array[0] = 0 insures a null character.
If the scan succeeded, n > 0 and code inspects the next character.
Alternative staying with scanf()
status = scanf("%3[a-z ]", temp_array);
// When nothing read, form "" string
if (status != 1) {
temp_array[0] = '\0';
}
bool success = true;
if (status == EOF) {
success = false;
} else {
// consume rest of line, noting if extra junk followed
int next_ch;
while ((next_ch = fgetc(stdin)) != '\n' && next_ch != EOF) {
success = false; //Extra junk
}
}
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.
So, I'm working on a simple hangman game in C, and I have the function read_guess, shown below.
void read_guess(char *guesses, char *p_current_guess)
{
int valid_guess = 0;
// Repeatedly takes input until guess is valid
while (valid_guess == 0)
{
printf(">>> ");
fgets(p_current_guess, 2, stdin);
if (!isalpha(*p_current_guess)) printf("Guesses must be alphabetic. Please try again.\n\n");
else
{
valid_guess = 1;
// Iterates over array of guesses and checks if letter has already been guessed
for (int i = 0; guesses[i] != '\0'; i++)
{
if (guesses[i] == *p_current_guess)
{
printf("You have already guessed this letter. Please try again.\n\n");
valid_guess = 0;
break;
}
}
}
}
}
I've tried all the standard input functions (including getchar), but with all of them, when an input larger than one character is supplied, instead of taking just the first character and moving on (or asking again), the rest of the input is "pushed back", and the next time input is requested, whether it be because the input contained a non-alphabetic character or the next round begins, the rest of the input is automatically processed. This repeats for each character of the input.
How can I avoid this?
You are using fgets which is good, but unfortunately not the right way...
fgets reads up to an end of line or at most 1 less the the number of character asked. And of course remaining characters are left for the next read operation...
The idiomatic way would be to ensure reading up to the end of line, whatever the length, or at least up to a much larger length.
Simple but could fail in more than SIZE characters on input:
#define SIZE 64
...
void read_guess(char *guesses, char *p_current_guess)
{
char line[SIZE];
int valid_guess = 0;
// Repeatedly takes input until guess is valid
while (valid_guess == 0)
{
printf(">>> ");
fgets(line, SiZE, stdin); // read a line of size at most SIZE-1
p_current_guess[0] = line[0]; // keep first character
p_current_guess[1] = '\0';
...
Robust but slightly more complex
/**
* Read a line and only keep the first character
*
* Syntax: char * fgetfirst(dest, fd);
*
* Parameters:
* dest: points to a buffer of size at least 2 that will recieve the
* first character followed with a null
* fd : FILE* from which to read
*
* Return value: dest if one character was successfully read, else NULL
*/
char *readfirst(dest, fd) {
#define SIZE 256 // may be adapted
char buf[SIZE];
char *cr = NULL; // return value initialized to NULL if nothing can be read
for (;;) {
if(NULL == fgets(buff, sizeof(buff), fd)) return cr; // read error or end of file
if (0 == strcspn(buff, "\n")) return cr; // end of file
if (cr == NULL) { // first read:
cr = dest; // prepare to return first char
dest[0] = buff[0];
dest[1] = 0;
}
}
}
You can then use it simply in your code:
void read_guess(char *guesses, char *p_current_guess)
{
int valid_guess = 0;
// Repeatedly takes input until guess is valid
while (valid_guess == 0)
{
printf(">>> ");
fgetfirst(p_current_guess, stdin);
You can discard all input until end-of-line, each time you want to ask for input.
void skip_to_eol(FILE* f, int c)
{
while (c != EOF && c != '\n')
c = fgetc(f);
}
...
char c = getchar(); // instead of fgets
skip_to_eol(stdin, c);
You can use getch() function on windows to get single character. and this is linux equivalent
What is the equivalent to getch() & getche() in Linux?
I've been trying to input 2 char arrays from user.
I want to truncate the input characters if they are more than specified length.
This is what I have done so far.
int main(){
printf("Enter Password: ");
char password[9]= {0};
fgets(password, sizeof(password), stdin);
printf("Enter key file path: ");
char file_path[200];
fflush(stdin);
fgets(file_path, sizeof(file_path), stdin);
puts(file_path);
return 0;
}
I get this output:
If I enter more than 8 chars, it automatically assigns charcaters above 8 to my file_path. It does not ask for the 2nd input!
PS: I tried scanf("%8s", password) instead of fgets. Same issue.
Please Help, Thanks
In OP's code, the input that does not fit in the first fgets() remains for subsequent input. Better code would consume the entire line and detect if the line is excessively long.
Use fgets() with a long enough buffer to look for incomplete line input.
Read at least 2 more characters: extra character and '\n'.
Perhaps use your own my_gets() to read a line.
// Read a line
// If input, without the \n fits in the destination, return `s`
// else return NULL
// Conditions: line != NULL, 0 < sz <= INT_MAX
char *my_gets(char *line, size_t sz) {
if (fgets(line, (int) sz, stdin) == NULL) {
line[0] = '\0';
return NULL; // EOF
}
size_t length = strlen(line);
if (length > 0 && line[length - 1] == '\n') {
line[--length] = '\0'; // Chop off \n
} else if (length == sz - 1) {
// Consume rest of line
bool looped = false;
int ch;
while ((ch = fgetc(stdin)) != '\n' && ch != EOF) {
looped = true;
}
if (looped) {
return NULL; // Line too long
}
}
return line;
}
Application
int main(void) {
printf("Enter Password: ");
char password[9];
if (my_gets(password, sizeof password) == NULL) {
return EXIT_FAILURE;
}
puts(password);
printf("Enter key file path: ");
char file_path[200];
if (my_gets(file_path, sizeof file_path) == NULL) {
return EXIT_FAILURE;
}
puts(file_path);
return EXIT_SUCCESS;
}
From a security standpoint, good to scrub password[] and line[] after code is done with it.
memset(password, 0, sizeof password);
Yet the call to fgets(), fgetc() are themselves not so secure as they are not specified to "cover their tracks" as they return. This is a deeper subject beyond this post.
How can I get a safe input of integer (especially, positive number) using scanf or gets? I've tried several solutions and each solution had some problems.
1. Using getchar() to remove string inputs
int safeInput() {
int input;
scanf("%d", &input);
while(getchar() != '\n');
return input;
}
This method effectively handles string inputs, however, if strings such as 3a are inputted, the value of input becomes 3, which is not a true exception handle.
2. Retrieving input as a string then converting to integer value.
int safeInput() {
char[200] input, safe_input;
gets(input);
// I know about the security issue about gets - but it's not the point.
int i = 0;
while (1) {
if (input[i] >= 48 && input[i] <= 57) safe_input[i] = input[i];
else break;
i++;
}
return atoi(safe_input);
}
This method has problem that it cannot handle if string that has longer length than allocated to input was inputted.
3. What if defining a string using pointer?
I concerned about defining input by pointer, like char *input;. However, once I executed gets(input)(or scanf("%s", input)), it raised runtime-error.
So what is a proper way to retrieve an integer value from console window using scanf or gets?
The answer depends on what exactly you mean by safe. If you want to catch any possible input error, your only option is to use a function of the strtol() family, which even allows for a range check. In my beginners' guide away from scanf(), I'm describing its use.
Here's the code adapted to what you're attempting here, with comments:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
// return success as boolean (0, 1), on success write result through *number:
int safeInput(int *number)
{
long a;
char buf[1024]; // use 1KiB just to be sure
if (!fgets(buf, 1024, stdin))
{
// reading input failed:
return 0;
}
// have some input, convert it to integer:
char *endptr;
errno = 0; // reset error number
a = strtol(buf, &endptr, 10);
if (errno == ERANGE)
{
// out of range for a long
return 0;
}
if (endptr == buf)
{
// no character was read
return 0;
}
if (*endptr && *endptr != '\n')
{
// *endptr is neither end of string nor newline,
// so we didn't convert the *whole* input
return 0;
}
if (a > INT_MAX || a < INT_MIN)
{
// result will not fit in an int
return 0;
}
// write result through the pointer passed
*number = (int) a;
return 1;
}
First if you want a safe input, do not use gets. Saying that you know about the issues is not a true excuse when you could use fgets. Next, the trick is to try to read a non blank character after the int: if you find no one, then there is nothing after the int on the line.
int safeInput(int *input) { // the return value is the indicator of failed read
int c;
char dummy[2]; // never forget the terminating null!
if (scanf("%d%1s", input, dummy) == 1) return 1;
// in case of error, skip anything up to end of line or end of file
while (((c = fgetc(stdin)) != '\n') && (c != EOF));
return 0;
}
The nice point here, is that when scanf returns 1, the %1s has eaten anything up to the end of line, including the terminating 'n'. But this has a major drawback: the scanf will only end on end of stream or after reading one additional (non blank) character. For that reason, Felix Palmen's answer is easier and safer to use.
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.