fgets prints out 'à' instead of an actual input - c

I am trying to write a program in C so that it takes input from the user which can only be 'Q' 'q' 'N' 'n' '1' or '2', everything else should be invalid. I am new to C thus I still can't figure out how fgets suppose to work. Whatever i enter as an input comes out as an 'à'.
char C=' ';
int N=0;
int flag=1;
char buffer[20];
char input[20];
printMenu();
printf("\n\nPlease choose something: ");
fgets(buffer, sizeof(buffer), stdin);
sscanf(buffer, "$s",&input);
//checking the input, shows à instead of an actual input
printf("Input is %c\n", input);
if(*input=='C'||*input=='c')
C=userInputChar();
else
if(*input=='N'||*input=='n')
N=userInputInt();
else
if(*input=='1')
printTriangleLeft(C,N);
else
if(*input=='2')
printTriangleRight(C,N);
else
if(input[0]=='Q'||input[0]=='q'){
printf("Exiting the program...");
return 0;
}
else
printf("Invalid input");

You're not checking the return value of sscanf, so you don't know whether it successfully parsed anything.
Your sscanf format string is $s, which doesn't extract any values. The &input argument is ignored. Besides, there is no scanf format that would take an argument of type char (*)[20] anyway.
printf %c takes an int. You're passing it a char *. This is why you're getting garbage output.
*input=='C' doesn't work either because *input is uninitialized at this point.

You should not pass array pointer into sscanf.
array itself is enough and $s is incorrect also. '%s' you should use to get string
Correct is like following:
sscanf(buffer, "%s", input);

Changed the following :
char buffer[20];
char input[20];
printf("\n\nPlease choose something: ");
fgets(buffer, sizeof(buffer), stdin);
sscanf(buffer, "$s",&input);
printf("Input is %c\n", input);
to
char buffer[20];
char input;
fgets(buffer, sizeof(buffer), stdin);
sscanf(buffer, "%c",&input);
printf("Input is %c\n", input);
seems to be working at least for one character

Continuing from my comment, in addition to the various syntax errors you have in your code, you are simply making this harder on yourself than it needs to be. Here, if I understand your question correctly, you want to take input and validate that it is made up of only the characters "Qqn12".
To validate your input is entirely made up on "Qqn12", you need to loop over each character in your input buffer and check that it is one of "Qqn12". If not the input is invalid.
Before we get there, lets talk about the proper way to validate and remove the '\n' included in the buffer filled by fgets (POSIX getline also includes the trailing '\n' in it buffer as well). To remove the trailing newline, you can either use strrchr to locate it, or you can just use strlen to get the length of the buffer and check that buffer[len - 1] == '\n'. If buffer[len - 1] is NOT equal to '\n' then you know character remain unread in stdin (because the number of chars in stdin either equal or exceed buffer size) and you need to handle that error. For example you can use the following:
char buf[MAXC] = "";
size_t len;
printf ("enter string: "); /* prompt, read and validate */
if (!fgets (buf, MAXC, stdin)) {
fprintf (stderr, "error: invalid input or user canceled.\n");
return 1;
}
len = strlen (buf); /* get buf length */
if (len && buf[len-1] == '\n') /* check for '\n' */
buf[--len] = 0; /* overwrite with '\0' */
else { /* input equals or exceeds buffer size, '\n' not read */
fprintf (stderr, "error: input exceed %d chars.\n", MAXC-2);
return 1;
}
(note the test condition len && buf[len-1] == '\n', you must check that len > 0 before testing buf[len-1] == '\n', or Undefined Behavior is invoked by attempting to read a negative array index.)
Next, you want to limit your input to just the chosen characters, so make a string literal containing the characters you will accept, e.g. char *accept = "Qqn12";. string.h provides the strchr function that will locate and return a pointer to the first occurrence of a given character c in a string s. The declaration is:
char *strchr(const char *s, int c);
Obviously, it does no good to look for one of the characters "Qqn12" in your buffer, because there could be other characters not "Qqn12" there too. However, if we turn the search around and ask "Is each character in buffer in accept (e.g. one of "Qqn12")", then you have exactly the test you are looking for -- and strchr does the work of scanning each char in accept for you. For example you could do the following:
...
char *accept = "Qqn12";
...
for (char *p = buf; *p; p++) /* for each char in buf */
if (!strchr (accept, *p)) { /* if not in 'accept', error */
fprintf (stderr, "error: invalid input '%c'.\n", *p);
return 1;
}
If you are more comfortable with array indexing than pointers, you can simply use array indexing to iterate over each character in buffer, e.g. the following does the exact same thing:
for (int i = 0; buf[i]; i++) /* for each char in buf */
if (!strchr (accept, buf[i])) { /* if not in 'accept', error */
fprintf (stderr, "error: invalid input '%c'.\n", buf[i]);
return 1;
}
(you can even use i < len instead of buf[i] for the exit condition if you like)
Putting all the pieces together, you can validate an entry made up of "Qqn12" is entered with something like the following:
#include <stdio.h>
#include <string.h>
#define MAXC 512
int main (void) {
char buf[MAXC] = "",
*accept = "Qqn12";
size_t len;
printf ("enter string: "); /* prompt, read and validate */
if (!fgets (buf, MAXC, stdin)) {
fprintf (stderr, "error: invalid input or user canceled.\n");
return 1;
}
len = strlen (buf); /* get buf length */
if (len && buf[len-1] == '\n') /* check for '\n' */
buf[--len] = 0; /* overwrite with '\0' */
else { /* input equals or exceeds buffer size, '\n' not read */
fprintf (stderr, "error: input exceed %d chars.\n", MAXC-2);
return 1;
}
for (char *p = buf; *p; p++) /* for each char in buf */
if (!strchr (accept, *p)) { /* if not in 'accept', error */
fprintf (stderr, "error: invalid input '%c'.\n", *p);
return 1;
}
printf ("valid input : %s\n", buf);
return 0;
}
Example Use/Output
$ ./bin/validinput
enter string: Qqn12n21Qq
valid input : Qqn12n21Qq
$ ./bin/validinput
enter string: Qqn12n21Qbq
error: invalid input 'b'.
Look things over and let me know if you have any further questions.

Related

Read lines from an input file of varying line sizes

Currently, I am using getline to read lines from a file and I can access individual characters the following way from stdin:
char buffer[1024];
while((lineSize = getline(&line, &len, stdin)) != -1) {
if (line[0] != 84) {
// ...
continue; // continue to next line in file
}
if (line[0] == 84){ // (the 'T' character)
printf("TEST: Line Value: %s\n", line);
buffer[0] = line[1]; // this is a single digit number in char form
buffer[1] = '\0';
// send buffer somewhere
send(clientSocket, buffer, strlen(buffer), 0);
// ...
}
A sample file is as follows:
T3
T9
S0
S4
T55
T6
However, as you can see, I run into issues when a number > 9 is given such as the T55 line here. I can only grab the first digit with this method. Therefore, I may have to completely redo the way I read a file. Is there a better and simple way I can read through an input file and check the first character and make the remaining character(s) into an int until the end of a line? (Max the integer can be is 100 btw)
Continuing from my comments, you can use fgets() to read line and then use sscanf() with the format string of " %c%d%n" to extract the first character and converting the next set of digits to an int and finally obtaining to total number of characters consumed by sscanf() in that conversion using the "%n" specifier. You validate that both the character and integer conversion took place and that the first non-whitespace character read was 'T'. You can then use mychar and myint as desired and use mylen as the length to use with send.
(note: you can scan forward in line to determine if any whitespace was included at the beginning and ignore than in your call to send() -- that is left to you)
Putting it altogether, you can so something like:
char line[1024],
mychar = 0;
int myint = 0,
mylen;
/* read using fgets */
while (fgets (line, sizeof line, stdin)) {
/* parse with sscanf, validate both conversions and 'T' as 1st char,
* use "%n" to get number of chars through int conversion
*/
if (sscanf (line, " %c%d%n", &mychar, &myint, &mylen) != 2 ||
mychar != 'T') {
fputs ("error: invalid format.\n", stderr);
continue;
}
send (clientSocket, line, mylen, 0); /* send mylen chars */
}
To be more specific, I will need to see your Minimal Complete Reproducible Example to ensure there is nothing outside what you have posted that will impact the code above.
Adding Example
Adding a short example to show the result of parsing under expected and unexpected input with the above, and adding the scanning forward to remove leading whitespace in the line, a short program that reads input from stdin and writes to stdout, outputting the lines matching 'T'(int), you could do:
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
int main (void) {
char line[1024],
mychar = 0,
nl = '\n';
int myint = 0,
mylen;
/* read using fgets */
while (fgets (line, sizeof line, stdin)) {
char *p = line;
/* parse with sscanf, validate both conversions and 'T' as 1st char,
* use "%n" to get number of chars through int conversion
*/
if (sscanf (line, " %c%d%n", &mychar, &myint, &mylen) != 2 ||
mychar != 'T') {
fprintf (stderr, "error: invalid format: %s", line);
continue;
}
while (isspace (*p)) { /* reamove leading whitespace */
p += 1;
mylen -= 1;
}
// send (clientSocket, p, mylen, 0); /* send mylen chars */
write (STDOUT_FILENO, p, mylen);
write (STDOUT_FILENO, &nl, 1);
}
}
(note: write (STDOUT_FILENO, &nl, 1); is simply included above to output a newline after each output -- it would not be part of what you send() over your socket -- unless the receiving program is using the '\n' as the line termination character)
Example Input File:
$ cat dat/charint.txt
T4
T44
T444
TT
T3 and more gibberish
P55
(note: leading whitespace and trailing characters included in last two lines beginning with 'T', including the invalid line format " TT")
Example Use/Output
$ ./bin/charandintsend < dat/charint.txt
T4
T44
T444
error: invalid format: TT
T3
error: invalid format: P55
Let me know if you have questions, or if I misunderstood some aspect of your question.

C: The first element of a char cannot be detected

I am learning getting inputs from key board. I want the user create a or more strings from the input, each string is considered as a line, the program will not terminate until a specified char is pressed. Then store these strings to the buffer.
However, when I print out the buffer, the first few elements of the string are always missing. Here is my code:
#include<stdio.h>
int main(void){
printf("Please type the string:\n");
char buffer[1000];
int c;
while( (c = getchar()) != ' ' ) {
fgets(buffer, sizeof(buffer), stdin);
printf("The output string is: \n%s\n", buffer);
if((c = getchar())== ' '){
printf("A space is detected!\n");
break;
}
}
}
The output is:
Please type the string:
abcdefg
The output string is:
bcdefg
hijklmn
The output string is:
jklmn
opqrst
The output string is:
qrst
A space is detected!
Program ended with exit code: 0
Which part did I go wrong? Any hints are very much appreciated.
The problem you are having is both getchar(), and fgets in your code are reading from stdin. Since you call getchar() first in your test, it was consuming the first character of your string, when you called it again, another character disappeared...
You don't need getchar() at all to end your loop. All you care about for breaking your loop as you have explained is whether the user enters a space as the first character. fgets does not skip leading whitespace, so any leading space entered by the user will be captured at the beginning of buffer. So to satisfy your loop-exit condition, all you need to do is check if the first character of buffer is a space.
How? The simple way is to just derererence buffer, e.g. *buffer returns the first character in buffer. How? In pointer notation, buffer + 0 is the offset you want in buffer, so to get the character at that location, you dereference, e.g. *(buffer + 0), which of course is just *buffer, which is the equivalent of buffer[0].
So, putting it altogether, and getting rid of getchar(), and adding strlen to properly validate that the string fit in buffer and to get the location for the trailing '\n' read and included in buffer by fgets (which leaves you with the length of trimmed string as a benefit), you could do something similar to:
#include <stdio.h>
#include <string.h>
#define MAXC 1000 /* if you need a constant, define one (or more) */
int main (void) {
char buffer[MAXC] = ""; /* initialize strings zero (good practice) */
for (;;) { /* loop continually taking input */
size_t len; /* variable for buffer length */
printf ("\nenter string: "); /* prompt */
if (!fgets (buffer, sizeof buffer, stdin)) /* read input */
break; /* exit if user cancels input */
len = strlen (buffer); /* get length */
if (len && buffer[len-1] == '\n') /* check if last char is \n */
buffer[--len] = 0; /* overwrite with nul-char */
else { /* otherwise string too long */
fputs ("error: string too long.\n", stderr);
return 1;
}
if (*buffer == ' ') /* check if 1st char of buffer is ' ' */
break;
printf ("buffer: %s (%zu chars)\n", buffer, len); /* output */
}
}
Example Use/Output
$ ./bin/fgetsspace
enter string: my dog has fleas
buffer: my dog has fleas (16 chars)
enter string: my cat has none
buffer: my cat has none (15 chars)
enter string: bye
(note: a space was entered before bye above, e.g. " bye")
Look things over and let me know if you have further questions.
Separating Words with strtok
To separate each line into individual words you can use strtok. The first argument is the buffer (for the 1st call), the second parameter is a list of characters to use as delimeters between the words (e.g. if you want to separate on space include a space, to not include the '.' at the end of a sentence include that as well -- and include the '\n'). After the 1st call to strtok all subsequent calls to get the remaining words uses NULL in place of buffer, e.g.
#include <stdio.h>
#include <string.h>
#define MAXC 1000 /* if you need a constant, define one (or more) */
int main (void) {
char buffer[MAXC] = ""; /* initialize strings zero (good practice) */
for (;;) { /* loop continually taking input */
size_t len; /* variable for buffer length */
char *delim = " .\n", /* delmiters for strtok */
*p = buffer; /* pointer to buffer for strtok */
printf ("\nenter string: "); /* prompt */
if (!fgets (buffer, sizeof buffer, stdin)) /* read input */
break; /* exit if user cancels input */
len = strlen (buffer); /* get length */
if (len && buffer[len-1] == '\n') /* check if last char is \n */
buffer[--len] = 0; /* overwrite with nul-char */
else { /* otherwise string too long */
fputs ("error: string too long.\n", stderr);
return 1;
}
if (*buffer == ' ') /* check if 1st char of buffer is ' ' */
break;
printf ("buffer: %s (%zu chars)\n", buffer, len); /* output */
p = strtok (buffer, delim); /* 1st call to strtok uses buffer */
while (p != NULL) {
printf (" %s\n", p);
p = strtok (NULL, delim); /* subsequent calls use NULL */
}
}
}
(note: the original buffer is modified, so make a copy if you need to preserve the original)
Example Use/Output
$ ./bin/fgetsspace
enter string: my dog has fleas
buffer: my dog has fleas (16 chars)
my
dog
has
fleas
enter string: my cat has none
buffer: my cat has none (15 chars)
my
cat
has
none
enter string: bye
getchar swallows up a character. Your first iteration gets one character swallowed up by the initial call in the while, and then successive iterations get two characters swallowed up, one by the getchar you use to detect a space and then again the one in the while.
Answering in addition to my initial comment and the issue:
First, quoting myself:
I believe that when using getChar(), you efficiently remove the character from stdin buffer.
As stated since then by other people, the problem is that your call to getchar function consume and input, efficiently removing it from stdin buffer.
See Jim Buck's answer for detailed informations on the precise behavior of your application.
Now, what should you do ?
First, the if inside the while loop is not necessary, and using your application right now must be pretty odd. Try doing :
#include<stdio.h>
int main(void){
printf("Please type the string:\n");
char buffer[1000];
int c;
while( (c = getchar()) != ' ' ) {
fgets(buffer, sizeof(buffer), stdin);
printf("The output string is: \n%s\n", buffer);
}
printf("A space is detected!\n");
}
Instead to prevent unnecessary user inputs. Your loop is basically an infinite loop so there is no need to check at the end of every iteration if the loop should terminate, the while statement is already doing that pretty damn well. :P
Now, to prevent the input from being taken out of buffer, I would consider using the buffer's first element instead of "c" variable.
Like so :
#include<stdio.h>
int main(void){
printf("Please type the strings:\n");
char buffer[1000];
while( (buffer[0] = getchar()) != ' ' ) { // Now reads directly into buffer
fgets(buffer + 1, sizeof(buffer), stdin); // + 1 prevents overriding the char we just read.
printf("The output string is: \n%s\n", buffer);
}
printf("A space is detected!\n");
}
Have a nice day!

Issues while getting user input in SCO Unix OS

I face a strange issue while trying to get user input through my code. I am pretty sure that the issue is not with the code but related to OS like standard input stream (stdin) or something like that, but since I don't have another machine with similar OS setup (as it's practically impossible to find an SCO machine nowadays), I expect some programmatic workarounds to solve this. My program reads a stream of alphanumeric characters from the user terminated by a '\n'.
But no matter how I try this to achieve this by different means, it just accepts the initial 256 characters. Initially I suspected the issue is with the fgets function , but when I use try to read the same value from a file using fgets, its working as expected.
Approach 1:
main()
{
char szInLine[999];
memset(szInLine, 0, sizeof(szInLine));
fprintf(stdout, "\nPlease enter the encrypted value:\n");
if (fgets(szInLine, 997, stdin) == NULL)
return(1);
fprintf(stdout, "Encrypted data string contains %i characters: %s\n",
strlen(szInLine), szInLine);
}
Approach 2:
while(ch = getc(stdin)) != EOF)
{
if((*szInLine++ = ch) == '\n')
{
break;
}
}
*szInLine = '\0';
fprintf(stdout, "Encrypted data string contains %i characters: %s\n", strlen(szInLine), szInLine);
Output for both cases : "Encrypted data string contains 256 characters: abcde.....
Other approaches I already tried but didn't succeed include changing the data type of the buffer which holds the value (from string to unsigned long), dynamically allocating memory to the buffer, setting stdin as unbuffered e.t.c.
OS environment :
SCO Unix, 32bit
Compiler:
CC
See the ioctl() and stty() manual page on the SCO web site. You should be able to retrieve the difference in the settings by testing terminal vs. redirection.
well, your programs (both) have errors:
/* you should include <stdio.h> so fgets() can return a char *,
* If you don't, it's assumed fgets() returns an int value. */
#include <stdio.h>
main()
{
char szInLine[999];
memset(szInLine, 0, sizeof(szInLine)); /* you don't need this */
fprintf(stdout, "\nPlease enter the encrypted value:\n");
/* fgets accepts a buffer and its size, it will reserve space for
* one '\0' char. */
if (fgets(szInLine, sizeof szInLine, stdin) == NULL) {
/* it is good to print some diagnostic if you receive EOF */
return(1);
}
fprintf(stdout, "Encrypted data string contains %i characters: %s\n",
strlen(szInLine), szInLine);
/* you should return 0, here */
return(0);
}
The second is even worse:
/* unbalanced parenthesis, you lack a parenthesis after 'while' keyword */
while(ch = getc(stdin)) != EOF)
{
if((*szInLine++ = ch) == '\n')
{
break;
}
}
*szInLine = '\0';
/* if you move the pointer 'szInLine' it will always be pointing to the end of
* the string, so this printf will show 0 characters and an empty string, you
* had better to save the pointer at the beginning, so you don't lose the
* reference to the string beginning.
*/
fprintf(stdout, "Encrypted data string contains %i characters: %s\n", strlen(szInLine), szInLine);
This should work:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char buffer_in[1000];
char buffer_out[1000];
while (fgets(buffer_in, sizeof buffer, stdin)) {
/* you'll get a line of up to 'sizeof buffer_in - 1' chars with an
* ending '\n' (or a truncated if the line has more than 'sizeof
* buffer_in - 1' chars. Also, you'll have a '\n' at the end of the
* buffer, if the line filled partially the buffer. */
fprintf(stderr,
"String read (%d chars): %s", /* this is why I don't put a '\n' here */
strlen(buffer_in),
buffer_in);
/* encrypt(buffer_in, sizeof buffer_in, buffer_out, sizeof buffer_out); */
}
/* here you got EOF */
return 0;
}
or if you want to use getc():
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
/* it is very important that c be an int, see manual
* page of fgetc(), getch() or getchar() */
int c;
char buffer[1000], *p = buffer;
/* we check for buffer length and for EOF. As we are doing the hard
* work ourselves, we have to check for 'sizeof buffer - 1' to allow
* space for the '\0'. */
while ((p < buffer + sizeof buffer - 1) && ((c = getchar()) != EOF)) {
if (c == '\n') { /* A NEWLINE, act on buffer, and skip it. */
*p = '\0'; /* end the string */
printf("Read %d chars: %s\n", p - buffer, buffer);
/* crypt it ... */
/* ... */
p = buffer; /* reset buffer */
continue;
}
*p++ = c; /* add the character to the buffer */
}
/* here you got EOF */
return 0;
}
One final note:
Don't post snippets of code, but complete examples, as it is very difficult to identify which errors are mistakes on copying the code here, or which are mistakes you have made in the original program.

Reading multiple lines with different data types in C

I have a very strange problem, I'm trying to read a .txt file with C, and the data is structured like this:
%s
%s
%d %d
Since I have to read the strings all the way to \n I'm reading it like this:
while(!feof(file)){
fgets(s[i].title,MAX_TITLE,file);
fgets(s[i].artist,MAX_ARTIST,file);
char a[10];
fgets(a,10,file);
sscanf(a,"%d %d",&s[i].time.min,&s[i++].time.sec);
}
However, the very first integer I read in s.time.min shows a random big number.
I'm using the sscanf right now since a few people had a similar issue, but it doesn't help.
Thanks!
EDIT: The integers represent time, they will never exceed 5 characters combined, including the white space between.
Note, I take your post to be reading values from 3 different lines, e.g.:
%s
%s
%d %d
(primarily evidenced by your use of fgets, a line-oriented input function, which reads a line of input (up to and including the '\n') each time it is called.) If that is not the case, then the following does not apply (and can be greatly simplified)
Since you are reading multiple values into a single element in an array of struct, you may find it better (and more robust), to read each value and validate each value using temporary values before you start copying information into your structure members themselves. This allows you to (1) validate the read of all values, and (2) validate the parse, or conversion, of all required values before storing members in your struct and incrementing your array index.
Additionally, you will need to remove the tailing '\n' from both title and artist to prevent having embedded newlines dangling off the end of your strings (which will cause havoc with searching for either a title or artist). For instance, putting it all together, you could do something like:
void rmlf (char *s);
....
char title[MAX_TITLE] = "";
char artist[MAX_ARTIST = "";
char a[10] = "";
int min, sec;
...
while (fgets (title, MAX_TITLE, file) && /* validate read of values */
fgets (artist, MAX_ARTIST, file) &&
fgets (a, 10, file)) {
if (sscanf (a, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
rmlf (title); /* remove trailing newline */
rmlf (artist);
s[i].time.min = min; /* copy to struct members & increment index */
s[i].time.sec = sec;
strncpy (s[i].title, title, MAX_TITLE);
strncpy (s[i++].artist, artist, MAX_ARTIST);
}
/** remove tailing newline from 's'. */
void rmlf (char *s)
{
if (!s || !*s) return;
for (; *s && *s != '\n'; s++) {}
*s = 0;
}
(note: this will also read all values until an EOF is encountered without using feof (see Related link: Why is “while ( !feof (file) )” always wrong?))
Protecting Against a Short-Read with fgets
Following on from Jonathan's comment, when using fgets you should really check to insure you have actually read the entire line, and not experienced a short read where the maximum character value you supply is not sufficient to read the entire line (e.g. a short read because characters in that line remain unread)
If a short read occurs, that will completely destroy your ability to read any further lines from the file, unless you handle the failure correctly. This is because the next attempt to read will NOT start reading on the line you think it is reading and instead attempt to read the remaining characters of the line where the short read occurred.
You can validate a read by fgets by validating the last character read into your buffer is in fact a '\n' character. (if the line is longer than the max you specify, the last character before the nul-terminating character will be an ordinary character instead.) If a short read is encountered, you must then read and discard the remaining characters in the long line before continuing with your next read. (unless you are using a dynamically allocated buffer where you can simply realloc as required to read the remainder of the line, and your data structure)
Your situation complicates the validation by requiring data from 3 lines from the input file for each struct element. You must always maintain your 3-line read in sync reading all 3 lines as a group during each iteration of your read loop (even if a short read occurs). That means you must validate that all 3 lines were read and that no short read occurred in order to handle any one short read without exiting your input loop. (you can validate each individually if you just want to terminate input on any one short read, but that leads to a very inflexible input routine.
You can tweak the rmlf function above to a function that validates each read by fgets in addition to removing the trailing newline from the input. I have done that below in a function called, surprisingly, shortread. The tweaks to the original function and read loop could be coded something like this:
int shortread (char *s, FILE *fp);
...
for (idx = 0; idx < MAX_SONGS;) {
int t, a, b;
t = a = b = 0;
/* validate fgets read of complete line */
if (!fgets (title, MAX_TITLE, fp)) break;
t = shortread (title, fp);
if (!fgets (artist, MAX_ARTIST, fp)) break;
a = shortread (artist, fp);
if (!fgets (buf, MAX_MINSEC, fp)) break;
b = shortread (buf, fp);
if (t || a || b) continue; /* if any shortread, skip */
if (sscanf (buf, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
s[idx].time.min = min; /* copy to struct members & increment index */
s[idx].time.sec = sec;
strncpy (s[idx].title, title, MAX_TITLE);
strncpy (s[idx].artist, artist, MAX_ARTIST);
idx++;
}
...
/** validate complete line read, remove tailing newline from 's'.
* returns 1 on shortread, 0 - valid read, -1 invalid/empty string.
* if shortread, read/discard remainder of long line.
*/
int shortread (char *s, FILE *fp)
{
if (!s || !*s) return -1;
for (; *s && *s != '\n'; s++) {}
if (*s != '\n') {
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF) {}
return 1;
}
*s = 0;
return 0;
}
(note: in the example above the result of the shortread check for each of the lines that make up and title, artist, time group.)
To validate the approach I put together a short example that will help put it all in context. Look over the example and let me know if you have any further questions.
#include <stdio.h>
#include <string.h>
/* constant definitions */
enum { MAX_MINSEC = 10, MAX_ARTIST = 32, MAX_TITLE = 48, MAX_SONGS = 64 };
typedef struct {
int min;
int sec;
} stime;
typedef struct {
char title[MAX_TITLE];
char artist[MAX_ARTIST];
stime time;
} songs;
int shortread (char *s, FILE *fp);
int main (int argc, char **argv) {
char title[MAX_TITLE] = "";
char artist[MAX_ARTIST] = "";
char buf[MAX_MINSEC] = "";
int i, idx, min, sec;
songs s[MAX_SONGS] = {{ .title = "", .artist = "" }};
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
for (idx = 0; idx < MAX_SONGS;) {
int t, a, b;
t = a = b = 0;
/* validate fgets read of complete line */
if (!fgets (title, MAX_TITLE, fp)) break;
t = shortread (title, fp);
if (!fgets (artist, MAX_ARTIST, fp)) break;
a = shortread (artist, fp);
if (!fgets (buf, MAX_MINSEC, fp)) break;
b = shortread (buf, fp);
if (t || a || b) continue; /* if any shortread, skip */
if (sscanf (buf, "%d %d", &min, &sec) != 2) { /* validate conversion */
fprintf (stderr, "error: failed to parse 'min' 'sec'.\n");
continue; /* skip line - tailor to your needs */
}
s[idx].time.min = min; /* copy to struct members & increment index */
s[idx].time.sec = sec;
strncpy (s[idx].title, title, MAX_TITLE);
strncpy (s[idx].artist, artist, MAX_ARTIST);
idx++;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (i = 0; i < idx; i++)
printf (" %2d:%2d %-32s %s\n", s[i].time.min, s[i].time.sec,
s[i].artist, s[i].title);
return 0;
}
/** validate complete line read, remove tailing newline from 's'.
* returns 1 on shortread, 0 - valid read, -1 invalid/empty string.
* if shortread, read/discard remainder of long line.
*/
int shortread (char *s, FILE *fp)
{
if (!s || !*s) return -1;
for (; *s && *s != '\n'; s++) {}
if (*s != '\n') {
int c;
while ((c = fgetc (fp)) != '\n' && c != EOF) {}
return 1;
}
*s = 0;
return 0;
}
Example Input
$ cat ../dat/titleartist.txt
First Title I Like
First Artist I Like
3 40
Second Title That Is Way Way Too Long To Fit In MAX_TITLE Characters
Second Artist is Fine
12 43
Third Title is Fine
Third Artist is Way Way Too Long To Fit in MAX_ARTIST
3 23
Fourth Title is Good
Fourth Artist is Good
32274 558212 (too long for MAX_MINSEC)
Fifth Title is Good
Fifth Artist is Good
4 27
Example Use/Output
$ ./bin/titleartist <../dat/titleartist.txt
3:40 First Artist I Like First Title I Like
4:27 Fifth Artist is Good Fifth Title is Good
Instead of sscanf(), I would use strtok() and atoi().
Just curious, why only 10 bytes for the two integers? Are you sure they are always that small?
By the way, I apologize for such a short answer. I'm sure there is a way to get sscanf() to work for you, but in my experience sscanf() can be rather finicky so I'm not a big fan. When parsing input with C, I have just found it a lot more efficient (in terms of how long it takes to write and debug the code) to just tokenize the input with strtok() and convert each piece individually with the various ato? functions (atoi, atof, atol, strtod, etc.; see stdlib.h). It keeps things simpler, because each piece of input is handled individually, which makes debugging any problems (should they arise) much easier. In the end I typically spend a lot less time getting such code to work reliably than I did when I used to try to use sscanf().
Use "%*s %*s %d %d" as your format string, instead...
You seem to be expecting sscanf to automagically skip the two tokens leading up to the decimal digit fields. It doesn't do that unless you explicitly tell it to (hence the pair of %*s).
You can't expect the people who designed C to have designed it the same way as you would. You NEED to check the return value, as iharob said.
That's not all. You NEED to read (and understand reelatively well) the entire scanf manual (the one written by OpenGroup is okay). That way you know how to use the function (including all of the subtle nuances of format strings) and what to do with the return vale.
As a programmer, you need to read. Remember that well.

compare multiple strings in single input in C

I currently have a piece of code that compares 1 input to a variable...
for example, when i input "GET" it compares it to the string i have set and then opens a file. but what if i want to compare more than one string in the input? such as if someone inputs "GET ./homepage.html"
so the first string GET indicates that they want to retrieve a file, and the second string "./homepage.html" is the file they want to view?
My thoughts on this would be to build an array with a combination of GET + all the possible file combinations and then use strcomp to choose the right one and open the specified file..? but i'm not 100% on how i would link my input to compare to a whole array?
current code is below, very basic string compare -> opens and writes file to stdout.
int main(int argc, char *argv[] ) {
MainStruct val;
parse_config(&val);
char userInput[100] = "success.txt";
char temp[100];
if (checkaccess() == 0)
{
parse_config(&val);
} else {
fprintf(stderr,"unable to load config file\n");
}
printf("Connected to Domain : %s", val.domain);
fgets(temp, 6, stdin);
temp[strcspn(temp, "\n")] = '\0';
if(strcmp(temp, "GET /") == 0 )
{
openfile(userInput);
} else {
printf("that was not a very valid command you gave\n");
}
}
EDIT: i should also mention that the second string input should also == userInput that the function openfile reads in. not sure how to separate out the two strings.
There are a couple of ways to approach this. The most straight forward is to read your full line of input into temp and then break temp into tokens with strtok or the like. The line of input would include your command (e.g. GET) and any other values you need to act upon. You can then make a decision if you received sufficient input (i.e. GET and a filename) to then respond as you intend.
Below is a short example that creates an array of char arrays (strings) to hold the tokens entered as temp. A couple of defines at the beginning limit each token to 64 chars and the maximum number of tokens to 5 (adjust as needed) It then breaks temp into tokens and checks whether the user entered more than 1 word. It then responds to GET and shows the filename it collected. (you can take whatever action you need). It also checks the number of tokens entered to make sure you don't try and write beyond the end of the array.
Take a look and let me know if you have questions:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXISZ 64
#define MAXIN 5
int main (void) {
char temp[MAXISZ] = {0}; /* temp variable */
char input[MAXIN][MAXISZ] = {{0}}; /* array of strings to hold input */
size_t icnt = 0; /* number of input words */
size_t tlen = 0; /* length of temp read by fgets */
printf ("\n Enter command [filename]: ");
if (fgets (temp, MAXISZ, stdin) == NULL) {
printf ("error: fgets failed.\n");
return 1;
}
/* get length and trim newline */
tlen = strlen (temp);
while (tlen > 0 && temp[tlen - 1] == '\n')
temp[--tlen] = 0;
/* if temp contains a space */
if (strchr (temp, ' '))
{
/* break tmp into tokens and copy to input[i] */
char *p = NULL;
for (p = strtok (temp, " "); p != NULL; p = strtok (NULL, " "))
{
strcpy (input[icnt++], p);
/* check if MAXIN reached */
if (icnt == MAXIN)
{
printf ("error: MAXIN token exceeded.\n");
break;
}
}
/* if more than 1 word input, use 1st as command, next as filename */
if (icnt > 0)
{
if (strcmp (input[0], "GET") == 0)
printf ("\n You can open file : %s\n", input[1]);
}
}
else
printf ("\n Only a single word entered as command '%s'.\n", temp);
printf ("\n");
return 0;
}
Output
$ ./bin/fgets_split
Enter command [filename]: GET /path/to/myfile.txt
You can open file : /path/to/myfile.txt
or
$ ./bin/fgets_split
Enter command [filename]: GET
Only a single word entered as command 'GET'.

Resources