strtok does not returns all the tokens - c

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);
}
}
}

Related

How do I use multiple fgets without them interfering with each other?

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.

What is the best way to match a string to specified format?

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

Checking Loop in C - scan() is not running by the second run

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 );

How to make the code to accept the next word not just the first one?

In this code, the problem which I am facing is, it only accepts the first word which has a comma at the end. The file has many words with commas at the end but it is accepting the first one. For example, if I gave the option to enter your ID card number which is not the first word. It could be the 2nd or 3rd word in the file then how I would handle it?
1st part
#define STRING_SIZE 49
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void RemoveNewLines( char * buffer ){
char * ptr;
ptr = strchr( buffer, '\n' );
if( ptr )
* ptr = 0;
ptr = strchr( buffer, '\r' ); // in case you see carriage returns also
if( ptr )
* ptr = 0;
}
2nd part
int main(){
char instr[STRING_SIZE+1], string[STRING_SIZE+1];
FILE * fr = NULL;
int flag = 0;
size_t length = 0;
fr = fopen("file.csv","r");
if( fr == NULL ){
printf( "Unable to open file\n" );
return 1;
}
printf("Enter your name: ");
fgets( instr, STRING_SIZE, stdin);
RemoveNewLines( instr );
strcat( instr, "," ); // append a comma to the user's entry
length = strlen( instr );
while( fgets( string, STRING_SIZE, fr ) ){
RemoveNewLines( string );
if( strncmp( instr, string, length ) == 0 ){
flag = 1;
break; } }
fclose(fr);
if( flag == 1 ) // I prefer positive logic{
printf( "Your details :" );
printf( "'%s'\n", string );
return 0;
}
printf("Access denied.\n");
return -1;
}
Well, you are comparing the beginning of a line in the file with whatever that was provided plus a , appended. So it is really what you asked it to do.
If you want to search on arbitrary fields, rather than this approach, I would split the line read from the csv, and compare the nth field with what was provided. Assuming this csv file is a basic one (no quotes or embedded commas/new-lines), you can easily do that by performing N strtok() operations on string.

How to get the second string of the input

#include <stdio.h>
#include <stdlib.h>
int main(void) {
char command[256];
char *token;
const char s[2] = " ";
fprintf(stdout, "$ Please enter a command \n");
fflush( stdout );
fgets ( command, 256, stdin );
token = strtok(command, s);
if (strcmp(token, "loaddungeon") == 0){
fprintf(stdout, "$ loaded successfully \n");
fflush( stdout );
}
}
I am trying to use strtok to get the second string of the input. For instance, if the input is "loaddungeon dfile.txt", what I want to get is the "dfile.txt". My function is able to get the string "loaddungeon". But I have no idea how to get the second string "dfile.txt". Can anyone tell me how to do it?
(Consider the input is always "loaddungeon dfile.txt".)
To read the second string, you need to pass NULL to strtok(). Keep in mind that fgets() retains the newline character from the input line, so you should change your delimiter definition from char s[2] = " "; to char s[] = " \r\n";, or char s* = " \r\n". This way the second token will not include any newline characters. Also note that strtok() returns a NULL pointer if no token is found, so the below code tests for this before printing the read tokens.
But, since you say that there are only two strings, I would consider just using sscanf() for this. Using the %s conversion specifier, sscanf() will read characters into a string until a whitespace character is encountered, but will not include this whitespace character in the string. When you use the %s specifier in a scanf() type function, you should specify a maximum field width to avoid buffer overflow. This maximum width should be one less than the size of the buffer to leave room for the '\0' string terminator, 255 in this case. The sscanf() function returns the number of successful assignments made, which should be 2 in this case. The sscanf() approach shown below (commented out) checks this return value before printing the strings.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_MAX 256
int main(void) {
char command[BUFFER_MAX];
char *token1 = NULL;
char *token2 = NULL;
const char *s = " \r\n";
fprintf(stdout, "$ Please enter a command \n");
fflush( stdout );
fgets ( command, BUFFER_MAX, stdin );
token1 = strtok(command, s);
token2 = strtok(NULL, s);
if (token1 && token2 && strcmp(token1, "loaddungeon") == 0) {
fprintf(stdout, "$ loaded successfully: %s\n", token2);
fflush( stdout );
}
/* or instead do this */
/*
char word1[BUFFER_MAX], word2[BUFFER_MAX];
if (sscanf(command, "%255s %255s", word1, word2) == 2) {
if (strcmp(word1, "loaddungeon") == 0){
fprintf(stdout, "$ loaded successfully: %s\n", word2);
fflush( stdout );
}
}
*/
return 0;
}
Every call to strtok will return a pointer to the last token found in the given string (or null if there is none left). To retrieve the second token with space as a delimiter, you need to call strtok for the second time.
int main()
{
char command[256];
char *token1 = NULL;
char *token2 = NULL;
const char s[2] = " ";
fprintf(stdout, "$ Please enter a command \n");
fflush(stdout);
fgets(command, 256, stdin);
token1 = strtok(command, s); // now points to first word
if (NULL != token1) {
token2 = strtok(NULL, s); // now points to second word
}
if (NULL != token2) {
if (strcmp(token2, "loaddungeon") == 0){
fprintf(stdout, "$ loaded successfully \n");
fflush(stdout);
}
}
}

Resources