Hi I am new to C and I want the user to type something like inspect 2 to show a value of an array at position 2 in that example.
I cant get it to work
char input[20];
scanf("%s", input);
if (strcmp(strtok(input, " "), "inspect") == 0) {
char str[20];
int idx;
printf("input was %s", input);
idx = sscanf(input, "%s %d", str, &idx);
}
it always prints input was inspect but the following space and number are not read?
What would be the right way to check if the user typed "inspect" and get the index he typed afterwards like I am trying to do?
thank you
You have few choices, and you want to choose one and not mix them up.
For reading the input, consider using the fgets. Much safer, with fewer exceptions to deal with. I've listed the equivalent sscanf, but it's much harder to use. They will both bring in a complete line to 'input'. Notice that the fgets will also include the trailing new line.
// make buffer large enough.
char input[255] ;
if ( fgets(input, sizeof(input), stdin) != NULL ) {
...
}
// OR
if ( sscanf("%19[^\n]", input) = 1 ) {
} ;
For parsing: few options to parse the input` string.
Between the option, I would vote for the sscanf, as it provides the most validation and protection against bad input, overflow, etc. The strcmp(strtok(...)) can easily result in SEGV errors, when strtok returns NULL.
Using sscanf
if ( sscanf(input, "inspect %d", &idx) ==1 ) {
... Show Element idx
} ;
Using strtok/strcmp
if ( strcmp(strtok(input, " "), "inspect") == 0 ) {
if ( sscanf("%d", strtok(NULL, " "), &idx) == 1 ) {
.. Show element idx
} ;
} ;
Using strtol
if ( strcmp(strtok(input, " "), "inspect") == 0 ) {
char *stptr = strtok(input, " "), *endptr = NULL ;
idx = strtol(stptr, &endptr, 10) ;
if ( endptr != stptr ) {
.. Show element idx
} ;
} ;
Related
int run_add_line_after(Document *doc, char *command) {
int paragraph_num, line_num;
char com[MAX_STR_SIZE + 1], extra[MAX_STR_SIZE + 2], line[MAX_STR_SIZE + 1];
if (sscanf(command, " %s %d %d %s", com, ¶graph_num, &line_num, extra)
== 4 && paragraph_num > 0 && line_num >= 0 && extra[0] == '*') {
strcpy(line, &(extra[1]));
if (add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
printf("add_line_after failed\n");
}
return SUCCESS;
}
return FAILURE;
}
I want sscanf to read everything left in command to extra but it's only taking the first word. For example, if I have:
command: "add_line_after 1 0 *first line of the document"
I want to see:
com: "add_line_after"
paragraph_num: 1
line_num: 0
extra: "first line of the document"
but instead I get:
com: "add_line_after"
paragraph_num: 1
line_num: 0
extra: "first"
because %s stops when it hits the space. How do I read the rest of the line while still ignoring any whitespace between '0' and '*'?
For reference, MAX_STR_SIZE is 80 and command is a 1025 character array (though I don't think that matters). Just assume extra is large enough to hold the rest of the line.
sscanf is really the wrong tool to use here. It can be done, but probably should not be used the way you are trying. Fortunately, you are not passing com to add_line_after, which means it is not necessary to ensure that com is a properly null-terminated string, and this allows you to avoid all of that unnecessary string copying. (If you were passing com, you would either have to copy it, or write a null terminator into command.) You don't want or need to use sscanf to move data at all. You can just use it to parse the numeric values. It's not clear to me if you want to discard any whitespace that follows the *, and this code does not. If you want to do so, removing that whitespace is trivial and left as an exercise for the reader:
int
run_add_line_after(struct document *doc, const char *command)
{
int paragraph_num, line_num, n;
const char *line;
if( 2 == sscanf(command, "%*s %d %d %n", ¶graph_num, &line_num, &n)
&& paragraph_num > 0 && line_num >= 0 && command[n] == '*' )
{
line = command + n + 1;
if( add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
fprintf(stderr, "add_line_after failed\n");
} else {
return SUCCESS;
}
}
return FAILURE;
}
The idea here is to simply use sscanf to figure out where the extra data is in the string and to parse the integer values. This is absolutely the wrong tool to use (the scanf family is (almost) always the wrong tool), and is used here only for demonstration.
-- edit --
But, of course, this doesn't do exactly what you want. It would be much cleaner to move some functionality into add_line_after to handle the newline, but since you also need to remove the newline, it becomes necessary to do something like:
int
run_add_line_after(struct document *doc, char *command)
{
int paragraph_num, line_num, n;
char *line;
if( 2 == sscanf(command, "%*s %d %d %n", ¶graph_num, &line_num, &n)
&& paragraph_num > 0 && line_num >= 0 && command[n] == '*' )
{
line = command + n + 1;
line[strcspn(line, "\n")] = '\0';
if( add_line_after(doc, paragraph_num, line_num, line) == FAILURE) {
fprintf(stderr, "add_line_after failed\n");
} else {
return SUCCESS;
}
}
return FAILURE;
}
This is not ideal. It would be better if you modify the API so that you can avoid both copying the data and modifying the string that you are given.
Got it. Format string should be " %s %d %d %[^\n]s". It will keep reading into the last string variable until it hits the enter key.
The format that I want to match the string to is "from:<%s>" or "FROM:<%s>". The %s can be any length of characters representing an email address.
I have been using sscanf(input, "%*[fromFROM:<]%[#:-,.A-Za-z0-9]>", output). But it doesn't catch the case where the last ">" is missing. Is there a clean way to check if the input string is correctly formatted?
You can't directly tell whether trailing literal characters in a format string are matched; there's no direct way for sscanf()) to report their absence. However, there are a couple of tricks that'll do the job:
Option 1:
int n = 0;
if (sscanf("%*[fromFROM:<]%[#:-,.A-Za-z0-9]>%n", email, &n) != 1)
…error…
else if (n == 0)
…missing >…
Option 2:
char c = '\0';
if (sscanf("%*[fromFROM:<]%[#:-,.A-Za-z0-9]%c", email, &c) != 2)
…error — malformed prefix or > missing…
else if (c != '>')
…error — something other than > after email address…
Note that the 'from' scan-set will match ROFF or MorfROM or <FROM:morf as a prefix to the email address. That's probably too generous. Indeed, it would match: from:<foofoomoo of from:<foofoomoo#example.com>, which is a much more serious problem, especially as you throw the whole of the matched material away. You should probably capture the value and be more specific:
char c = '\0';
char from[5];
if (sscanf("%4[fromFROM]:<%[#:-,.A-Za-z0-9]%[>]", from, email, &c) != 3)
…error…
else if (strcasecmp(from, "FROM") != 0)
…not from…
else if (c != '>')
…missing >…
or you can compare using strcmp() with from and FROM if that's what you want. The options here are legion. Be aware that strcasecmp() is a POSIX-specific function; Microsoft provides the equivalent stricmp().
Use "%n". It records the offset of the scan of input[], if scanning got that far.
Use it to:
Detect scan success that include the >.
Detect Extra junk.
A check of the return value of sscanf() is not needed.
Also use a width limit.
char output[100];
int n = 0;
// sscanf(input, "%*[fromFROM:<]%[#:-,.A-Za-z0-9]>", output);
sscanf(input, "%*[fromFROM]:<%99[#:-,.A-Za-z0-9]>%n", output);
// ^^ width ^^
if (n == 0 || input[n] != '\0') {
puts("Error, scan incomplete or extra junk
} else [
puts("Success");
}
If trailing white-space, like a '\n', is OK, use " %n".
Regarding the first part of the string, if you want to accept only FROM:< or from:< , then you can simply use the function strncmp with both possibilities. Note, however, that this means that for example From:< will not be accepted. In your question, you implied that this is how you want your program to behave, but I'm not sure if this really is the case.
Generally, I wouldn't recommend using the function sscanf for such a complex task, because that function is not very flexible. Also, in ISO C, it is not guaranteed that character ranges are supported when using the %[] format specifier (although most common platforms probably do support it). Therefore, I would recommend checking the individual parts of the string "manually":
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
bool is_valid_string( const char *line )
{
const char *p;
//verify that string starts with "from:<" or "FROM:<"
if (
strncmp( line, "from:<", 6 ) != 0
&&
strncmp( line, "FROM:<", 6 ) != 0
)
{
return false;
}
//verify that there are no invalid characters before the `>`
for ( p = line + 6; *p != '>'; p++ )
{
if ( *p == '\0' )
return false;
if ( isalpha( (unsigned char)*p ) )
continue;
if ( isdigit( (unsigned char)*p ) )
continue;
if ( strchr( "#:-,.", *p) != NULL )
continue;
return false;
}
//jump past the '>' character
p++;
//verify that we are now at the end of the string
if ( *p != '\0' )
return false;
return true;
}
int main( void )
{
char line[200];
//read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Input failure!\n" );
exit( EXIT_FAILURE );
}
//remove newline character
line[strcspn(line,"\n")] = '\0';
//call function and print result
if ( is_valid_string ( line ) )
printf( "VALID\n" );
else
printf( "INVALID\n" );
}
This program has the following output:
This is an invalid string.
INVALID
from:<john.doe#example.com
INVALID
from:<john.doe#example.com>
VALID
FROM:<john.doe#example.com
INVALID
FROM:<john.doe#example.com>
VALID
FROM:<john.doe#example!!!!.com>
INVALID
FROM:<john.doe#example.com>invalid
INVALID
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 );
I have the following snippet of code:
char *buffer2 = malloc(1024*sizeof(char));
scanf("%s",buffer2);
char *command = strtok(buffer2," ");
if (strcmp(command,"INFO") == 0)
{
char *file_path = strtok(NULL," ");
if (file_path != NULL)
{
info(file_path);
}
}
My goal is : given a string like "CMD_NAME ARG1 ARG2" ... to tokenize the string according this format. So far so good, I have written the above code, it compiles, the program does not crash ,but the outcome is ,however, wrong in the sense that if I input the string "INFO work/file1.sf" the command is parsed properly but for the file_path the strtok function returns a NULL pointer. I have been struggling with this for hours , I have read the documentation for this function, I have searched other responses on this subject ,but none of the them were satisfactory. If you have a clue of what I'm doing wrong please help me.
Given the following code:
char *buffer2 = malloc(1024*sizeof(char));
scanf("%s",buffer2);
char *command = strtok(buffer2," ");
if (strcmp(command,"INFO") == 0)
{
char *file_path = strtok(NULL," ");
if (file_path != NULL)
{
info(file_path);
}
}
the expression 'sizeof(char)' is defined by the standard as 1.
Multiplying anything by 1 will not change its' value
and has no effect on the parameter passed to malloc().
However, it does clutter the code, making understanding, debugging, and maintenance more difficult.
Suggest removing that expression.
when calling scanf()
always check the returned value to assure the operation was successful.
when using the "%s" format specifier..
must use a max input characters modifier, so the user cannot overrun the input buffer2
the "%s" format specifier will stop inputting when encountering any 'white space'
The call to strtok() will not find anything but the whole input line.
because the input will have stopped before inputting any spaces
because a space is one of the 'white space' characters.
Suggest the following code:
#define BUFFER_LEN (1024)
char *buffer2 = NULL;
if( NULL == (buffer2 = malloc( BUFFER_LEN ) ) )
{ // then malloc failed
perror( "malloc for 1024 bytes failed")
exit( EXIT_FAILURE );
}
// implied else, malloc successful
if( !fgets( buffer2, BUFFER_LEN, stdin ) )
{// then, fgets failed
perror( "fgets from stdin failed");
exit( EXIT_FAILURE )''
}
// implied else, fgets successful
char *command = NULL;
if( NULL != (command = strtok(buffer2," \n") ) )
{
if (strcmp(command,"INFO") == 0)
{
char *file_path = NULL;
if( NULL != (file_path = strtok(NULL," \n") ) )
{
info(file_path);
}
}
}
Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int individualAverage(int data[][20],int j)
{
int k,average=0;
for(k=0;k<10;k++)
{
average += data[k][j];
}
return average;
}
int main()
{
int var,indAvg=0;
int i=0,j,k;
char *experiments[20];
int data[10][20];
char str[100],str2[100];
char *ptr, *token;
int no_line=1;
while(fgets(str,100,stdin) != NULL && (strcmp(str,"*** END ***") && strcmp(str,"*** END ***\n")))
{
if(no_line % 2 == 0)
{
k=0;
token = strtok (str," ");
while (token != NULL)
{
sscanf (token, "%d", &var);
data[k++][i] = var;
token = strtok (NULL," ");
}
i++;
}
else
{
ptr = strdup(str);
experiments[i] = ptr;
}
no_line++;
}
fgets(str,100,stdin);
token = strtok(str," ");
while(token != NULL && (strcmp(token,"4") && strcmp(token,"4")))
{
sscanf (token, "%d", &var);
printf("DATA SET ANALYSIS\n1.\tShow all the data\n2.\tCalculate the average for an experiment\n3.\tCalculate the average across all experiments\n4.\tQuit\nSelection: %d\n\n",var);
switch(var)
{
case 1 :
for(j=0;j<i;j++)
{
printf("%s",experiments[j]);
for(k=0;k<10;k++)
{
printf("%d ",data[k][j]);
}
printf("\n");
}
printf("\n");
break;
case 2 :
printf("What experiment would you like to use?\n");
token = strtok (NULL," ");
sscanf (token, "%s", &str);
for(j=0;j<i;j++)
{
if(strcmp(experiments[j],str) == 0)
{
indAvg = individualAverage(data,j);
printf("Experiment: %s",experiments[j]);
printf("The individual average of the experiment is %d\n",indAvg);
break;
}
}
}
token = strtok(NULL," ");
}
}
OK, so I have a method that takes lines of redirection input. The lines come in pairs. First line is the name of an experiment, and the second line has the 10 values separated by spaces for that experiment. After these pairs, there is an ending line "*** END ***"
After this line, there is one last line holding the instructions of what to do with the data.
I'm currently having a problem where I've used fgets() to store the strings of the first pairs of lines into a variable which I declared as char *experiments[20];
Each of strings that this array is pointing to will have '\n' at the end of the string because of fgets()
Back to the last line of instructions. You have values 1-4. Right now I'm looking at instruction 2. It tells the average of an experiment. So after 2 on the last line, there must be the name of one of the experiments. I've used:
char str[100];
int var;
char *token;
token = strtok(str, " ");
sscanf (token, "%d", &var);
to get the first value on the line into var (pretend it's 2). So after that would be a string. Say it's Test 1, I'll use
token = strtok (NULL," ");
sscanf (token, "%s", &str);
to get the value into str, and then I'll compare it to experiments for all possible indexes.
HOWEVER, because fgets() gives '\n' at the end of the lines, all of the experiments strings will have '\n' at the end while str will just have the name of the experiment WITHOUT '\n' therefore they will never be equal even if '\n' is the only difference between the strings.
Any solutions?
Since you know that there may be a \n at the end of the string, you could check for it, and remove it if it's there:
size_t len = strlen(str);
if (len != 0 && str[len-1] == '\n') {
str[len-1] = '\0';
}
This would terminate the line at \n, so your strcmp would succeed. An alternative is to use strncmp, and pass the length of the target string. This runs the risk of false positives when there's a longer suffix that \n, though.
You could also read your data like this:
fscanf(f, "%99[^\n]", str);
You can make your own version of fgets that doesn't store the new-line character when it encounters one, and call it myfgets. Something like this would replicate fgets's behaviour, I think, produced with respect to the description given in MSDN:
char * myfgets( char * str, int n, FILE * stream ) {
if ( n <= 0 ) return NULL; // won't accept less than or equal to zero length
int currentPos = 0;
while ( n-- > 0 ) {
int currentChar = fgetc( stream );
if ( currentChar == EOF ) return NULL;
if ( currentChar == '\n' ) break;
// if these two lines were in reversed order,
// it would behave same as the original fgets
str[currentPos++] = currentChar;
}
return str;
}
But of course the other solution is simpler, hehe...