Swap characters in string using pointer - c

I'm trying to create a program that receives a word and two chars to be replaced inside that word using pointers, but I cannot figure it out how.
my .h file:
typedef struct pointer{
char message[100];
char start;
char end;
} tPointer;
tPointer readMsg();
my .c file:
tPointer readMsg(){
tPointer nPointer;
printf("Input word, char start and char end (between spaces): ");
scanf("%s %c %c", nPointer.message, &nPointer.start, &nPointer.end);
return(nPointer);
}
my main.c file:
int main(){
tPointer pointer;
pointer = readMsg();
printf("%s, %c, %c\n", pointer.message, pointer.start, pointer.end);
for (int i = 0; pointer.message[i] != '\0'; ++i) {
if (pointer.start == pointer.message[i])
{
printf("tem: %c\n", pointer.message[i]);
}
}
return 0;
}
The start input is the character inside message that I want to change, the end input is the character I will trade with start to create the new message
I have to swap the characters inside that for loop, right?
Example1:
message = stack
start = a
end = b
new message = stbck
Example2:
message = overflow
start = o
end = z
new message = zverflzw

From your updated examples, it looks like you want to find the first occurrence of start and replace that character with end. Much of the confusion with your question stems from the fact that start and end have nothing to do with what it is you are attempting to do. Use descriptive names to help keep your logic straight, e.g.
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
typedef struct {
char message[MAXC]; /* don't use magic-numbers, use a constant */
char find; /* use descriptive names */
char replace;
} tPointer;
(note: the struct-tag pointer was unused and omitted)
With your tPointer readMsg() function, you have no way to tell if the input succeeded or if the string and two characters requested were actually entered. It is critical that you validate EVERY input and validate the input matches what was requested.
Rather than returning type tPointer (which if uninitialized provides no way to tell what was entered), pass a tPointer pointer as a parameter and return type int with 0 on failure and 1 on success (or vice-versa -- up to you). That way you can check the return in the caller and know whether readMsg() succeeded or failed, e.g.
/* choose return type that can indicate success/failure
* of input operation. Returns 1 on success, 0 otherwise
*/
int readMsg (tPointer *tp)
{
char buf[MAXC]; /* buffer to hold line of input */
/* read input into buf */
fputs ("Input word, find char, replace char (between spaces): ", stdout);
if (!fgets (buf, MAXC, stdin)) { /* always validate EVERY input */
puts ("(user canceled input)");
return 0;
}
/* separate using sscanf() -- always validate correct input */
if (sscanf (buf, "%s %c %c", tp->message, &tp->find, &tp->replace) != 3) {
fputs ("error: invalid input.\n", stderr);
return 0;
}
return 1; /* return success */
}
(note: the user generating a manual EOF to cancel input by pressing Ctrl + d (or Ctrl + z on windows) is valid input by the user that should be handled)
Also note, that a larger buffer was used to read the entire line into buf and then separate the string and characters in buf using sscanf(). This insures that if any EXTRA characters are entered by the user they are read and discarded. Failure to do so would leave any extra characters in stdin (unread) just waiting to bite you on your next attempted read (such as if you were doing the read in a loop to let the user enter more than one set of data)
Since you wrote a function for readMsg(), you may as well write another for replaceChar(). The return type can be int again and you can pass your tPointer pointer as a parameter, returning 1 on success or 0 if the find character was not found in message, e.g.
/* replace 'find' char with 'replace' char.
* returns 1 on success, 0 if find char not found.
*/
int replaceChar (tPointer *tp)
{
for (int i = 0; tp->message[i]; i++) { /* loop over each char */
if (tp->message[i] == tp->find) { /* check if find char */
tp->message[i] = tp->replace; /* replace it */
return 1; /* return success */
}
}
return 0; /* return failure - char not found */
}
Now in main() check the return of each function to ensure both succeed before outputting the results. On failure, handle the error. You can do something similar to:
int main (void) {
tPointer ptr = { .message = "" }; /* initialize struct all zero */
if (!readMsg (&ptr)) { /* validate read */
return 1; /* handle failure */
}
if (!replaceChar (&ptr)) { /* validate replacement */
fprintf (stderr, "error: char not found '%c'.\n", ptr.find);
return 1; /* handle failure */
}
/* output results */
printf ("find = %c\nreplace = %c\nupdated string: %s\n",
ptr.find, ptr.replace, ptr.message);
}
(note: you can paste the 4-pieces of code together for the complete program)
Example Use/Output
A valid replacement:
$ ./bin/tpointer-find-replace
Input word, find char, replace char (between spaces): Overflow o E
find = o
replace = E
updated string: OverflEw
Invalid input:
$ ./bin/tpointer-find-replace
Input word, find char, replace char (between spaces): Overflow o
error: invalid input.
Character Not Found:
$ ./bin/tpointer-find-replace
Input word, find char, replace char (between spaces): Overflow s y
error: char not found 's'.
User cancels input by generating manual EOF:
$ ./bin/tpointer-find-replace
Input word, find char, replace char (between spaces): (user canceled input)
Let me know if you have further questions.

Related

How can I stop taking inputs from user when I press the key 'q'?

I researched about this issue all day long and saw all other similar questions but somehow I miss something and my code doesn't work at all. And there is an error I've been noticing; when program sorts numbers in ascending order generally doesn't read first and last number. I just got curious about why this problem occurs and how can I make it work. I guess my code is ridiculous entirely so I don't want to waste your time if you can give some tips it would be enough for me :)
#include <stdio.h>
int main ()
{
int i,b,c,n;
int number[200];
char option;
do
{
printf("Please enter numbers you wanna input: ");
scanf("%d", &n);
for(int i=0; i<n; ++i) {
scanf("%d",&number[i]);
}
option = getchar ();
while(getchar() != '\n');
}
while (option != 'q');
for (i=0; i<n; ++i)
{
for (b=i+1; b<n; ++b)
{
if (number[i]>number[b])
{
c=number[i]; //i used c as temp.
number[i]=number[b];
number[b]=c;
}
}
}
printf("Sorted order of given numbers is this: \n");
for (i=0; i<n; ++i)
printf("%d\n",number[i]);
return 0; }
As #MikeCAT explained, your immediate problem is with:
do
{
printf("Please enter numbers you wanna input: ");
scanf("%d", &n);
for(int i=0; i<n; ++i) {
scanf("%d",&number[i]);
}
option = getchar (); /* reads '\n' left in stdin by scanf */
while(getchar() != '\n'); /* stdin empty -- getchar() blocks waiting for next input */
} while (option != 'q');
The real problem it discloses is scanf() is full of pitfalls and should be avoided for user-input until you understand it well enough to know why it should not be used for user-input. That means knowing each conversion specifier and whether or not it will consume leading whitespace, and further understanding what occurs on a matching failure and what you must do to discard offending input to correct the issue. This doesn't even reach the failure-to-validate EVERY input AND conversion.
These are just the tip of the iceberg as far a scanf() pitfalls go -- which is why all new C programmers are encouraged to use line-oriented input function such as fgets() or POSIX getline() for all user-input. The primary benefit is that line-oriented input functions consume an entire line of user-input each time -- so the potential for leaving characters unread in stdin just waiting to bite you on your next input is eliminated (presuming a reasonable size array is provided for input with fgets())
Using fgets() For User-Input
It doesn't matter what type input function you use, you cannot use any of the correct unless you check the return. Unless you check the return you have no determination whether you user-input succeeded or failed. If you blindly use the variable you think holds the input before you validate the input succeeded -- you are asking for Undefined Behavior. Rule: validate EVERY input AND EVERY conversion...
So how to use fgets()? It is really easy. Simply provide a buffer (character array) large enough to hold the longest expected user input and then MULTIPLY by 4 (or something reasonable). The pointer being DON'T SKIMP ON BUFFER SIZE. You would rather be 10,000 characters too long than 1-character too short. For general user a 1K buffer is fine (1024 bytes). That even protects you if the cat steps on the keyboard.
If you are programming on a micro-controller with limited memory, then reduce the buffer size to 2-times the largest anticipated input. (keep the cats away)
When you need to convert a number from the character array filled, the simplest way is to call sscanf() using the buffer as input (similar to how you are using scanf()). But the benefit here is that it does not matter if the conversion fails, it doesn't leave anything unread in stdin (the read has already taken place, so there is no possibility of the conversion affecting the state of your input stream). If you allow multiple integers to be entered at the same time, then strtol() can handle working from the start to end of your buffer converting values as it goes.
The only caveat when reading string input with a line-oriented function is the function will read and include the '\n' as part of the buffer it fills. You want to remove it if you are storing that value as a string. You can do that with strcspn() which returns the number of characters before any of the characters contained in its reject list. So you simply use "\n" as your reject list and it tells you the number of characters up to the newline. Then you simply use that number to overwrite the '\n' with '\0' trimming the '\n' from the end. For example if you were reading into the buffer called line, then the following is all you need to trim the '\n' from the end of the input:
char line[1024];
if (fgets (line, sizeof line, stdin))
line[strcspn (line, "\n")] = 0;
(note: in your case where you are simply converting what is contained in line to a number -- there is no need to trim the '\n' anyway, sscanf() will ignore whtiespace, and a '\n' is whitespace)
So in your case what would reading your input entail? Simply declare a buffer (character array) to hold the user-input and use fgets() handle all user-input from the user. You can re-use the same buffer for all inputs. You can make life easier on yourself by creating a short function that takes the array to fill and a prompt as parameters. Then if the prompt isn't NULL display the prompt and read the user-input -- checking the return.
If fgets() returns NULL it means EOF was reached before any input was received (and it is perfectly valid for the user to cancel input by generating a manual EOF with Ctrl + d, or Ctrl + z on windows). So check the return, if the user canceled input, just handle that gracefully. You could write a getstr() function to help as:
#include <stdio.h>
#include <stdlib.h> /* for qsort */
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXI 200
#define PROMPT "No. of intgers to read (max 200): "
...
/* fill array s with string, display prompt if not NULL
* returns pointer to string on success, NULL otherwise
*/
char *getstr (char *s, const char *prompt)
{
if (prompt) /* if prompt not NULL, display */
fputs (prompt, stdout);
if (!fgets (s, MAXC, stdin)) { /* read entire line of input */
puts ("(user canceled input)"); /* handlie EOF case */
return NULL;
}
return s; /* convenience return of s for immediate use if needed */
}
int main (void) {
char buf[MAXC]; /* array to hold ALL input */
int number[MAXI] = {0}, /* initialize all arrays */
i = 0, n = 0;
while (i == 0 || i != n) { /* loop until numbers filled */
if (!getstr (buf, PROMPT)) /* validate EVERY user-input */
return 0;
...
Allowing The User To Quit With 'q' (or pressing [Enter] on an empty line)
When filling an array with fgets() that makes responding to any character and taking special actions very, very easy. You have just filled a character array. If you want to check for a special character -- just check the first character (element) in the array! That means all you need to check is the character in buf[0] (or equivalently just *buf -- which is short for *(buf + 0) in pointer notation)
So to allow the user to quit if they entered 'q' (or pressed return on an emply line) all you need is:
if (buf[0] == 'q' || *buf == '\n') /* exit on 'q' or empty-line */
return 0;
You can use this at any point in main(). If you were checking in a function, you would just choose a return that would indicate the user quit (like with a return type of int, just pick an integer, say -1 to indicate the user quit, save 0 for some other failure and 1 to indicate success). But since your logic is in main() simply returning from main() is fine. Since quitting isn't an error, return 0; (equivalent to exit (EXIT_SUCCESS);).
Converting Integer Value From The Array
As mentioned above, after reading with fgets(), if the user didn't quit, then your next job is to convert the digits in the buffer to an integer value. Using sscanf() similar to how you are attempting to use scanf() is fine. The only difference with sscanf() is it takes the buffer holding the digits as its first argument, e.g.
if (sscanf (buf, "%d", &n) != 1) { /* validate EVERY conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
Here if the conversion fails, you handle the error and just continue back to the top of the loop, allowing the user to "try again". If the conversion succeeds, your validation isn't over yet. The input must be a positive value, greater than 0 and less than or equal to 200 -- otherwise with 0 there is nothing to enter, anything less or more than 200 and you would invoke Undefined Behavior attempting to write before or beyond your array bounds. Just add that validation:
if (n <= 0 || 200 < n) { /* validate input in range */
fprintf (stderr, " error: out of range, (0 < n <= %d)\n", MAXI);
continue;
}
Same as with the last conversion, if the user got it wrong, handle the error and continue; allowing the user to "try again". Once you have a valid number for the number of integers you will save in the array, reading the individual values is exactly the same as reading the number of integer to enter. Here you just loop until the user has entered all the values correctly:
for (i = 0; i < n;) { /* loop reading n integers */
printf ("number[%2d]: ", i+1); /* prompt */
if (!getstr(buf, NULL) || /* read/validate input */
*buf == 'q' || *buf == '\n') /* quit on 'q' or empty line */
return 0;
if (sscanf (buf, "%d", &number[i]) != 1) { /* validate conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
i += 1; /* only increment on good input */
}
(note: how the value for i is only incremented if the user provides valid-input)
Sorting In C (qsort)
You are learning C. No matter what you need to sort, C provides qsort() as part of the standard library that will sort any array you need (and is much more fully tested and less error prone than trying to "whip up a sort yourself out of thin air....". qsort() sorts arrays of any type object. The only thing that makes new C programmers eyes roll back in their heads is the need to write a compare function that tells qsort() how to sort your array. (it's really quite simply once your eyes roll back forward)
Every compare function has the same declaration:
int compare (const void *a, const void *b)
{
/* cast a & b to proper type,
* return: -1 - if a sorts before b
* 0 - if a & b are equal
* 1 - if b sorts before a
*/
}
const void *what?? Relax. a and b are just pointers to elements in your array. (qsort() uses a void* type so it can pass any type object to the compare function) Your job in writing the compare function is just to cast them back to the proper type and then write the logic above. In your case a is a pointer to int (e.g. int*), so all you need to do is cast to (int*) and then dereference the pointer to get the integer value, e.g.
/* qsort compare function, sort integers ascending
* using result of (a > b) - (a < b) prevents overflow
* use (a < b) - (a > b) for descending.
*/
int cmpint (const void *a, const void *b)
{
int ia = *(int*)a, /* a & b are pointers to elements of the array to sort */
ib = *(int*)b; /* cast to correct type and dereference to obtain value */
return (ia > ib) - (ia < ib); /* return difference: -1 - a sort before b
* 0 - a and b are equal
* 1 - b sorts before a
*/
}
(note: simply returning ia - ib would work, but it would be subject to integer overflow if ia was a large negative value and ib a large positive value or vice-versa. So you use the difference of two comparison operations to eliminate that chance -- try it, pick two numbers for ia and ib and see how that works out...)
Now if my array is number and I have n elements in it and I named my compare function cmpint how difficult is it to use qsort() to sort the values in the number array??
qsort (number, n, sizeof *number, cmpint); /* sort numbers */
(done!)
Putting It Altogether*
If you wrap it all up into your program, you would end up with:
#include <stdio.h>
#include <stdlib.h> /* for qsort */
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXI 200
#define PROMPT "No. of intgers to read (max 200): "
/* qsort compare function, sort integers ascending
* using result of (a > b) - (a < b) prevents overflow
* use (a < b) - (a > b) for descending.
*/
int cmpint (const void *a, const void *b)
{
int ia = *(int*)a, /* a & b are pointers to elements of the array to sort */
ib = *(int*)b; /* cast to correct type and dereference to obtain value */
return (ia > ib) - (ia < ib); /* return difference: -1 - a sort before b
* 0 - a and b are equal
* 1 - b sorts before a
*/
}
/* fill array s with string, display prompt if not NULL
* returns pointer to string on success, NULL otherwise
*/
char *getstr (char *s, const char *prompt)
{
if (prompt) /* if prompt not NULL, display */
fputs (prompt, stdout);
if (!fgets (s, MAXC, stdin)) { /* read entire line of input */
puts ("(user canceled input)"); /* handlie EOF case */
return NULL;
}
return s; /* convenience return of s for immediate use if needed */
}
int main (void) {
char buf[MAXC]; /* array to hold ALL input */
int number[MAXI] = {0}, /* initialize all arrays */
i = 0, n = 0;
while (i == 0 || i != n) { /* loop until numbers filled */
if (!getstr (buf, PROMPT)) /* validate EVERY user-input */
return 0;
if (buf[0] == 'q' || *buf == '\n') /* exit on 'q' or empty-line */
return 0;
if (sscanf (buf, "%d", &n) != 1) { /* validate EVERY conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
if (n <= 0 || 200 < n) { /* validate input in range */
fprintf (stderr, " error: out of range, (0 < n <= %d)\n", MAXI);
continue;
}
for (i = 0; i < n;) { /* loop reading n integers */
printf ("number[%2d]: ", i+1); /* prompt */
if (!getstr(buf, NULL) || /* read/validate input */
*buf == 'q' || *buf == '\n') /* quit on 'q' or empty line */
return 0;
if (sscanf (buf, "%d", &number[i]) != 1) { /* validate conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
i += 1; /* only increment on good input */
}
}
qsort (number, n, sizeof *number, cmpint); /* sort numbers */
puts ("\nsorted values:"); /* output results */
for (i = 0; i < n; i++)
printf (i ? " %d" : "%d", number[i]); /* ternary to control space */
putchar ('\n'); /* tidy up with newline */
}
Example Use/Output
Used as intended:
$ ./bin/fgets_n_integers+sort
No. of intgers to read (max 200): 10
number[ 1]: 321
number[ 2]: 8
number[ 3]: -1
number[ 4]: 4
number[ 5]: -2
number[ 6]: 0
number[ 7]: -123
number[ 8]: 123
number[ 9]: 6
number[10]: 2
sorted values:
-123 -2 -1 0 2 4 6 8 123 321
Abused with bad input (intentionally) and using 'q' to quit in the middle:
$ ./bin/fgets_n_integers+sort
No. of intgers to read (max 200): bananas
error: invalid integer input.
No. of intgers to read (max 200): 0
error: out of range, (0 < n <= 200)
No. of intgers to read (max 200): 201
error: out of range, (0 < n <= 200)
No. of intgers to read (max 200): 5
number[ 1]: bananas again!!!!!!!!!!!!!!!!!
error: invalid integer input.
number[ 1]: twenty-one
error: invalid integer input.
number[ 1]: 21
number[ 2]: 12
number[ 3]: done
error: invalid integer input.
number[ 3]: really
error: invalid integer input.
number[ 3]: q
When you write any input routine -- go try and break it! If it fails, figure out why, fix it and try again. When you have tried every bad corner-case you can think of and your input routine continues to work -- you can feel reasonably good about it -- until the user you give it to does something totally screwy and finds a new corner case (fix that too)
The Corner-Case Left For You
What happens if the user wants to enter 1 number in the array? Is there any reason to sort? Go back and look at the code and figure out where you could either prevent the user from entering 1 as a valid input OR, just output what the first number the user enters and skip sorting, etc.. -- up to you.
As always, this ended up way, way longer than I anticipated. But seeing where you were stuck on your program -- there no short way to help you see what you need to do and why without a few extra (dozen) of paragraphs. So there is a lot here. SLOW DOWN, digest it, have a talk with the duck (See How to debug small programs -- don't laugh, it works)
And then, if you still have additional questions, drop a comment below and I'm happy to help further.
If you are entering newline characters after numbers, scanf("%d",&number[i]); will leave newline characters inputted in the buffer and it is read by option = getchar ();. Therefore, option cannot correctly get desired user input.
The part
option = getchar ();
while(getchar() != '\n');
should be
scanf(" %c", &option);
Note for the space before %c. This means that whitespace characters (including newline characters9 should be ignored.
#include <stdio.h>
int main ()
{
int i,b,c,n;
int number[200];
char option;
char cont;
do
{
x:
printf("\nPlease enter numbers you wanna input: ");
scanf("%d", &n);
for(i=0; i<n; i++) {
scanf("%d",&number[i]);
}
option = getchar();
}
while (option != 'q');
for (i=0; i<n; i++)
{
for (b=i+1; b<n; b++)
{
if (number[i]>number[b])
{
c=number[i]; //i used c as temp.
number[i]=number[b];
number[b]=c;
}
}
}
printf("Sorted order of given numbers is this: \n");
for (i=0; i<n; i++)
printf("%d\n",number[i]);
printf("Do you want to continue? Press k : (press any key to turn it off) ");
cont = getche();
if(cont == 'k')
goto x;
else
exit(0);
return 0;
}
I think the error is in the index process of array, for and do-while loop. I made a few small changes. I have added "goto" for convenience. I changed the index values of loop and I fixed do while loop.

C For loop skips first iteration and bogus number from loop scanf

I am creating a mailing label generator for school and am having an issue with a few problems. My program is to take the full name, address, city, state, and zip code for individuals from 0 to 10. When running my program I am having two major problems. The for loop skips the full name "safergets()" and moves on to the address safergets. I moved on to see if everything else works, but my verification for the zip code would not work correctly. I added a printf to see if the input was the same number and found it to be bogus. Also, I am getting an error code for my line attempting to capitalize the state output. I'm sure I am using toupper incorrectly. Attached below is my code, error code, and output.
#include <stdio.h>
#include <ctype.h>
/* Define structure */
struct information
{
char full_name[35], address[50], city[25], state[3];
long int zip_code;
};
/* Function safer_gets */
/* ------------------- */
void safer_gets (char array[], int max_chars)
{
/* Declare variables. */
/* ------------------ */
int i;
/* Read info from input buffer, character by character, */
/* up until the maximum number of possible characters. */
/* ------------------------------------------------------ */
for (i = 0; i < max_chars; i++)
{
array[i] = getchar();
/* If "this" character is the carriage return, exit loop */
/* ----------------------------------------------------- */
if (array[i] == '\n')
break;
} /* end for */
/* If we have pulled out the most we can based on the size of array, */
/* and, if there are more chars in the input buffer, */
/* clear out the remaining chars in the buffer. */
/* ---------------------------------------------------------------- */
if (i == max_chars )
if (array[i] != '\n')
while (getchar() != '\n');
/* At this point, i is pointing to the element after the last character */
/* in the string. Terminate the string with the null terminator. */
/* -------------------------------------------------------------------- */
array[i] = '\0';
} /* end safer_gets */
/* Begin main */
int main()
{
/* Declare variables */
struct information person[10];
int x, i;
/* Issue greeting */
printf("Welcome to the mailing label generator program.\n\n");
/* Prompt user for number of individuals between 0 - 10. If invalid, re-prompt */
do
{
printf("How many people do you want to generate labels for (0-10)? ");
scanf("%i", &x);
if(x<0 || x>10)
printf("Invalid number. Please re-enter number. Must be from 0 to 10.\n");
}while(x<0 || x>10);
/* Begin loop for individual information */
for(i = 0; i < x; i++)
{
printf("\n\nEnter name: ");
safer_gets(person[i].full_name, 35); /* This is the step being skipped */
printf("\nEnter street address: ");
safer_gets(person[i].address, 50);
printf("\nEnter city: ");
safer_gets(person[i].city, 25);
printf("\nEnter state: ");
gets(person[i].state);
/* Begin loop to verify correct zipcode */
do
{
printf("\nEnter zipcode: ");
scanf("%ld", person[i].zip_code); /* I get a bogus number here */
if(person[i].zip_code<00001 || person[i].zip_code>99999)
{
printf("\nInvalid zipcode. Must be from 00001 to 99999.");
}
}while(person[i].zip_code<00001 || person[i].zip_code>99999);
/* end loop */
}/* end of loop */
/* Output individual information in mailing format, condition for 0 individuals */
if(x>0 && x<10)
{
printf("\n\nBelow are your mailing labels:\n\n");
}
/* Begin loop for outputting individual(s) mailing labels */
for(i = 0; i < x; i++)
{
printf("%s\n",person[i].full_name);
printf("%s\n",person[i].address);
printf("%s\n",person[i].city);
/* Output state in all uppercase */
printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */
printf("%.5ld\n\n", person[i].zip_code);
} /* end of loop */
printf("Thank you for using the program.\n");
}/*end of main */
Error code:142: warning: passing arg 1 of `toupper' makes integer from pointer without a cast.
Output:
Welcome to the mailing label generator program.
How many people do you want to generate labels for (0-10)? 1
Enter name:
Enter street address: 100 Needhelp Ave.
Enter city: Gardner
Enter state: NY
Enter zipcode: 01420
Invalid zipcode. Must be from 00001 to 99999.
Enter zipcode:
I have looked at several questions on here to try and understand where I am going wrong, but if feel it might be a couple of issues effecting my program. Also, the safergets function was given to my class by our professor to ensure that the user does not input more characters than the array can hold.
Thank you for your help and tolerance in helping me understand my mistakes!
Let's take a look at the problem one by one:
newline Remains in stdin after No. or Persons Read
printf("\n\nEnter name: ");
safer_gets(person[i].full_name, 35); /* This is the step being skipped */
It is skipped because your safer_gets() reads only to the first '\n' (newline character -- not a carriage-return, that is '\r'). However, the first character saver_gets() sees in the input stream is the '\n' character that remains in stdin unread after your call to scanf in:
printf("How many people do you want to generate labels for (0-10)? ");
scanf("%i", &x);
All scanf format specifiers for numeric conversion read only through the last digit (or decimal point) that makes up a number leaving the '\n' generated by the user pressing Enter unread in the input-stream (stdin here). This is one of the primary reasons new C programmers are encouraged to read user input with a line-oriented input function such as fgets() (or POSIX getline()) and then use sscanf to parse values from the filled buffer.
Why Line-Oriented Input Functions are Preferred for User-Input
By using a line-oriented input function with sufficient buffer, the complete line of user-input is consumed (including the '\n' from the user pressing Enter). This ensures that stdin is ready for the next input and doesn't have unread characters left-over from a prior input waiting to bite you.
Using All Input Functions Correctly
If you take nothing else from this answer, learn this -- you cannot use any input function correctly unless you check the return. This is especially true for the scanf family of functions. Why? If you are attempting to read an integer with scanf and the user enters "four" instead, then a matching failure occurs and character extraction from your input stream ceases with the first invalid character leaving all offending characters in your input stream unread. (just waiting to bite you again).
Using scanf Correctly
scanf can be used, if used correctly. This means you are responsible for checking the return of scanf every time. You must handle three conditions
(return == EOF) the user canceled input by generating a manual EOF by pressing Ctrl+d (or on windows Ctrl+z);
(return < expected No. of conversions) a matching or input failure occurred. For a matching failure you must account for every character left in your input buffer. (scan forward in the input buffer reading and discarding characters until a '\n' or EOF is found); and finally
(return == expected No. of conversions) indicating a successful read -- it is then up to you to check whether the input meets any additional criteria (e.g. positive integer, positive floating-point, within a needed range, etc..).
You also must account for what is left in your input stream after a successful read with scanf. As discussed above, scanf will leave the '\n' in the input stream unread for ALL conversion specifiers unless you specifically account for it in your format string (which, if accounted for, usually results in a fragile input format string, easily foiled by additional extraneous characters after the wanted input but before the '\n') When using scanf for input, you must put your accountant's hat on and account for every character that remains in the input stream and empty the input stream of any offending characters when required.
You can write a simple empty_stdin() function to handle removing all extraneous characters that remain after a user input by simply scanning forward discarding all characters that remain until the '\n' is found or EOF encountered. You do that to a varying degree in your safer_gets() function. You can write a simple function as:
void empty_stdin(void)
{
int c = getchar(); /* read character */
while (c != '\n' && c != EOF) /* if not '\n' and not EOF */
c = getchar(); /* repeat */
}
You can do the same thing with a simple for loop inline, e.g.
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
Next Problem -- Attempting to Write to Invalid Address
When using scanf, scanf expects the parameter for a corresponding conversion to be a pointer to the appropriate type. In:
printf("\nEnter zipcode: ");
scanf("%ld", person[i].zip_code); /* I get a bogus number here */
You fail to provide a pointer, providing a long int value instead. Since person[i].zip_code is type long int in order to provide a pointer for scanf to fill you must use the address-of operator, e.g. &person[i].zip_code to tell scanf which address to fill with the value it provides a conversion for.
Wait? Why don't I have to do that with array? On access, an array is converted to a pointer to the first element. So for string input, if an array is being used to hold the string, it is automatically converted to a pointer C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3).
toupper Operates on Characters not Strings
printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */
As discussed in my comment, toupper takes type int as the parameter, not type char*. To convert a string to upper/lower case, you need to loop over each character converting each character individually. However, in your case with the .state member of your struct, there are only 2 characters to worry about, so just convert them both when they are read, e.g.
/* just 2-chars to convert to upper - do it here */
person[i].state[0] = toupper (person[i].state[0]);
person[i].state[1] = toupper (person[i].state[1]);
Fundamental Problems in safer_gets()
That takes care of the most of the obvious problems, but the safer_gets() function itself has several fundamental problems. Specifically, it fails to handle EOF when returned by getchar() and it fails to provide any indication to the user whether the requested user-input succeeded or failed due to returning nothing with type void. In any function you write, where there is any possibility of failure within the function, you MUST provide a meaningful return to the calling function to indicate whether the requested operation of the function succeeded or failed.
What can you do with safer_gets()? Why not return a simple int value providing the number of characters read on success, or -1 (the normal value for EOF) on failure. You get the double-bonus of now being able to validate whether the input succeeded -- and you also get the number of character in the string (limited to 2147483647 chars). You also now have the ability to handle a user canceling the input by generating a manual EOF with Ctrl+d on Linux or Ctrl+z (windows).
You should also empty stdin of all characters input in ALL cases other than EOF. This ensures there are no characters that remain unread after your call to safer_gets() that can bite you if you make a call to another input function later. Making those changes, you could write your safer_gets() as:
/* always provide a meaninful return to indicate success/failure */
int safer_gets (char *array, int max_chars)
{
int c = 0, nchar = 0;
/* loop while room in array and char read isn't '\n' or EOF */
while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF)
array[nchar++] = c; /* assing to array, increment index */
array[nchar] = 0; /* nul-terminate array on loop exit */
while (c != EOF && c != '\n') /* read/discard until newline or EOF */
c = getchar();
/* if c == EOF and no chars read, return -1, otherwise no. of chars */
return c == EOF && !nchar ? -1 : nchar;
}
(note: above the test on nchar + 1 < max_chars ensures that a character remains for the nul-terminating character, and is just a safer rearrangement of nchar < max_chars - 1)
General Approach to Input Validation
Now, you have an input function you can use that indicates success/failure of input allowing you to validate the input back in the calling function (main() here). Take for example reading the .full_name member using safer_gets(). You can't just blindly call safer_gets() and not know whether input was canceled or a premature EOF encountered and use then proceed to use the string it filled with any confidence in your code. *Validate, validate, validate each expression. Back in main(), you can do that by calling safer_gets() as follows to read .full_name (and every other string variable):
#define NAMELEN 35 /* if you need a constant, #define one (or more) */
#define ADDRLEN 50 /* (don't skimp on buffer size) */
...
for (;;) { /* loop continually until valid name input */
fputs ("\nEnter name : ", stdout); /* prompt */
int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if name empty - handle error */
fputs (" error: full_name empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
(note: the return of safer_gets() is captured in the variable rtn and then evaluated for -1 (EOF), 0 empty-string, or greater than 0, good input)
You can do that for each string variable you need to use, and then use the same principals discussed above to read and validate .zip_code. Putting it altogether in a short example, you could do:
#include <stdio.h>
#include <ctype.h>
#define NAMELEN 35 /* if you need a constant, #define one (or more) */
#define ADDRLEN 50 /* (don't skimp on buffer size) */
#define CITYLEN 25
#define STATELEN 3
#define PERSONS 10
struct information {
char full_name[NAMELEN],
address[ADDRLEN],
city[CITYLEN],
state[STATELEN];
long int zip_code;
};
/* always provide a meaninful return to indicate success/failure */
int safer_gets (char *array, int max_chars)
{
int c = 0, nchar = 0;
/* loop while room in array and char read isn't '\n' or EOF */
while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF)
array[nchar++] = c; /* assing to array, increment index */
array[nchar] = 0; /* nul-terminate array on loop exit */
while (c != EOF && c != '\n') /* read/discard until newline or EOF */
c = getchar();
/* if c == EOF and no chars read, return -1, otherwise no. of chars */
return c == EOF && !nchar ? -1 : nchar;
}
int main (void) {
/* declare varaibles, initialize to all zero */
struct information person[PERSONS] = {{ .full_name = "" }};
int i = 0, x = 0;
puts ("\nWelcome to the mailing label generator program.\n"); /* greeting */
for (;;) { /* loop continually until a valid no. of people entered */
int rtn = 0; /* variable to hold RETURN from scanf */
fputs ("Number of people to generate labels for? (0-10): ", stdout);
rtn = scanf ("%d", &x);
if (rtn == EOF) { /* user generated manual EOF (ctrl+d [ctrl+z windows]) */
puts ("(user canceled input)");
return 0;
}
else { /* either good input or (matching failure or out-of-range) */
/* all required clearing though newline - do that here */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
if (rtn == 1) { /* return equals requested conversions - good input */
if (0 <= x && x <= PERSONS) /* validate input in range */
break; /* all checks passed, break read loop */
else /* otherwise, input out of range */
fprintf (stderr, " error: %d, not in range 0 - %d.\n",
x, PERSONS);
}
else /* matching failure */
fputs (" error: invalid integer input.\n", stderr);
}
}
if (!x) { /* since zero is a valid input, check here, exit if zero requested */
fputs ("\nzero persons requested - nothing further to do.\n", stdout);
return 0;
}
/* Begin loop for individual information */
for (i = 0; i < x; i++) { /* loop until all person filled */
/* read name, address, city, state */
for (;;) { /* loop continually until valid name input */
fputs ("\nEnter name : ", stdout); /* prompt */
int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if name empty - handle error */
fputs (" error: full_name empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid street input */
fputs ("Enter street address : ", stdout); /* prompt */
int rtn = safer_gets(person[i].address, ADDRLEN); /* read address */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if address empty - handle error */
fputs ("error: street address empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid city input */
fputs ("Enter city : ", stdout); /* prompt */
int rtn = safer_gets(person[i].city, CITYLEN); /* read city */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if city empty - handle error */
fputs ("error: city empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid state input */
fputs ("Enter state : ", stdout); /* prompt */
int rtn = safer_gets(person[i].state, STATELEN); /* read state */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if state empty - handle error */
fputs ("error: state empty.\n", stderr);
continue; /* try again */
}
else { /* good input */
/* just 2-chars to convert to upper - do it here */
person[i].state[0] = toupper (person[i].state[0]);
person[i].state[1] = toupper (person[i].state[1]);
break;
}
}
/* read/validate zipcode */
for (;;) { /* loop continually until valid zipcode input */
fputs ("Enter zipcode : ", stdout); /* prompt */
int rtn = scanf ("%ld", &person[i].zip_code); /* read zip */
if (rtn == EOF) { /* user pressed ctrl+d [ctrl+z windows] */
puts ("(user canceled input)");
return 1;
}
else { /* handle all other cases */
/* remove all chars through newline or EOF */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
if (rtn == 1) { /* long int read */
/* validate in range */
if (1 <= person[i].zip_code && person[i].zip_code <= 99999)
break;
else
fprintf (stderr, " error: %ld not in range of 1 - 99999.\n",
person[i].zip_code);
}
else /* matching failure */
fputs (" error: invalid long integer input.\n", stderr);
}
}
}
/* Output individual information in mailing format, condition for 0 individuals */
for(i = 0; i < x; i++)
/* you only need a single printf */
printf ("\n%s\n%s\n%s, %s %ld\n", person[i].full_name, person[i].address,
person[i].city, person[i].state, person[i].zip_code);
fputs ("\nThank you for using the program.\n", stdout);
}
(note: by using #define to create the needed constants, if you need to adjust a number, you have a single place to make the change and you are not left picking though each variable declaration and loop limit to try and make the change)
Example Use/Output
When you have finished writing any input routine -- go try and break it! Find the corner-cases that fail and fix them. Keep trying to break it by intentionally entering incorrect/invalid input until it no longer excepts anything but what the user is required to input. Exercise your input routines, e.g.
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 3
Enter name : Mickey Mouse
Enter street address : 111 Disney Ln.
Enter city : Orlando
Enter state : fL
Enter zipcode : 44441
Enter name : Minnie Mouse
Enter street address : 112 Disney Ln.
Enter city : Orlando
Enter state : Fl
Enter zipcode : 44441
Enter name : Pluto (the dog)
Enter street address : 111-b.yard Disney Ln.
Enter city : Orlando
Enter state : fl
Enter zipcode : 44441
Mickey Mouse
111 Disney Ln.
Orlando, FL 44441
Minnie Mouse
112 Disney Ln.
Orlando, FL 44441
Pluto (the dog)
111-b.yard Disney Ln.
Orlando, FL 44441
Thank you for using the program.
Respecting the users wish to cancel input at any point when they generates a manual EOF with Ctrl+d on Linux or Ctrl+z (windows), you should be able to handle that from any point in your code.
At the first prompt:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): (user canceled input)
Or at any prompt thereafter:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 3
Enter name : Mickey Mouse
Enter street address : 111 Disney Ln.
Enter city : (user canceled input)
Handle a request for zero person:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 0
zero persons requested - nothing further to do.
(**personally, I would just change the input test and have them enter a value from 1-10 instead)
Invalid input:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): -1
error: -1, not in range 0 - 10.
Number of people to generate labels for? (0-10): 11
error: 11, not in range 0 - 10.
Number of people to generate labels for? (0-10): banannas
error: invalid integer input.
Number of people to generate labels for? (0-10): 10
Enter name : (user canceled input)
You get the point... Bottom line, you must validate every user input and know it is valid before making use of the input in your program. You cannot validate any input from any function unless you check the return. If you take away nothing except that, the learning has been worthwhile.
Look things over and let me know if you have further questions. (and ask your prof. how safer_gets() handles EOF and how you are supposed to validate whether the funciton succeeded or failed)

how to get specific rows from csv file with exact data using c?

This is my csv file, i want to get only those row which start with character "A" so i got my output but with some addition column as '0' please help me to find were i went wrong?
And one more thing i want to remove specific column like bread,anName,ot
Name,id,bread,anName,Ot,number
A,1,animal,tiger,op,8.1
M,2,animal,toper,ip,9.1
A1,7,animal,dog,cp,Na11
A2,9,animal,mouse,ap,0
A23,9,animal,pouch,gp,Na11
#include <stdio.h>
#include <stdlib.h>
#define NUMLETTERS 100
typedef struct {
char Name[100];
int id;
char number[100];
} record_t;
int main(void) {
FILE *fp;
record_t records[NUMLETTERS];
int count = 0, i;
fp = fopen("letter.csv", "r");
if (fp == NULL) {
fprintf(stderr, "Error reading file\n");
return 1;
}
while (fscanf(fp, "%s,%d,%s", records[count].name, &records[count].id, records[count].number) == 1)
count++;
for (i = 0; i < count; i++) {
if(records[i].Name[0] == 'A'){
printf("%s,%d,%s\n", records[i].Name, records[i].id, records[i].number);
}
}
fclose(fp);
return 0;
}
i want output as:
A,1,8.1
A1,7,Na11
A2,9,0
A23,9,Na11
You have two problems:
The %s format specifier tells fscanf to read a space-delimited string. Since the the records aren't space-delimited the first %s will read the whole line.
The fscanf function returns the number of successfully parsed elements it handled. Since you attempt to read three values you should compare with 3 instead of 1.
Now for one way how to solve the first problem: Use the %[ format specifier. It can handle simple patterns and, most importantly, negative patterns (read while input does not match).
So you could tell fscanf to read a string until it finds a comma by using %[^,]:
fscanf(fp, " %[^,],%d,%s", records[count].Refdes, &records[count].pin, records[count].NetName)
The use of the %[ specifier is only needed for the first string, as the second will be space-delimited (the newline).
Also note that there's a space before the %[ format, to read and ignore leading white-space, like for example the newline from the previous line.
i want to get only those row which start with character "A"
i want to remove the number which coming between A and tiger,
If I understand you correctly and you only want to store rows beginning with 'A', then I would adjust your approach to read each line with fgets() and then check whether the first character in the buffer is 'A', if so, continue; and get the next line. The for those lines that do start with 'A', simply use sscanf to parse the data into your array of struct records.
For your second part of removing the number between 'A' and "tiger", there is a difference between what you store and what you output (this comes into play in storing only records beginning with 'A' as well), but for those structs stored where the line starts with 'A', you can simply not-output the pin struct member to get the output you want.
The approach to reading a line at a time will simply require that you declare an additional character array (buffer), called buf below, to read each line into with fgets(), e.g.
char buf[3 * NUMLETTERS] = "";
...
/* read each line into buf until a max of NUMLETTERS struct filled */
while (count < NUMLETTERS && fgets (buf, sizeof buf, fp)) {
record_t tmp = { .Refdes = "" }; /* temporary struct to read into */
if (*buf != 'A') /* if doesn't start with A get next */
continue;
/* separate lines beginning with 'A' into struct members */
if (sscanf (buf, " %99[^,],%d,%99[^\n]",
tmp.Refdes, &tmp.pin, tmp.NetName) == 3)
records[count++] = tmp; /* assign tmp, increment count */
else
fprintf (stderr, "%d A record - invalid format.\n", count + 1);
}
A short example putting that to use and (since we are not sure what "remove" is intended to be), we have included a pre-processor conditional that will only output the .Refdes and .NetName members by default, but if you either #define WITHPIN or include the define in your compile string (e.g. -DWITHPIN) it will output the .pin member as well.
#include <stdio.h>
#include <stdlib.h>
#define NUMLETTERS 100
typedef struct {
char Refdes[NUMLETTERS];
int pin;
char NetName[NUMLETTERS];
} record_t;
int main (int argc, char **argv) {
record_t records[NUMLETTERS];
char buf[3 * NUMLETTERS] = "";
int count = 0, i;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* read each line into buf until a max of NUMLETTERS struct filled */
while (count < NUMLETTERS && fgets (buf, sizeof buf, fp)) {
record_t tmp = { .Refdes = "" }; /* temporary struct to read into */
if (*buf != 'A') /* if doesn't start with A get next */
continue;
/* separate lines beginning with 'A' into struct members */
if (sscanf (buf, " %99[^,],%d,%99[^\n]",
tmp.Refdes, &tmp.pin, tmp.NetName) == 3)
records[count++] = tmp; /* assign tmp, increment count */
else
fprintf (stderr, "%d A record - invalid format.\n", count + 1);
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (i = 0; i < count; i++)
#ifdef WITHPIN
printf ("%-8s %2d %s\n",
records[i].Refdes, records[i].pin, records[i].NetName);
#else
printf ("%-8s %s\n", records[i].Refdes, records[i].NetName);
#endif
}
Example Use/Output
$ ./bin/getaonly dat/getasonly.txt
A tiger
A1 dog
A2 mouse
A23 pouch
If you define -DWITHPIN in your compile string, then you will get all three outputs:
$ ./bin/getaonly dat/getasonly.txt
A 1 tiger
A1 7 dog
A2 9 mouse
A23 9 pouch
(note: with the data stored in your array, you can adjust the output format to anything you need)
Since there is some uncertainty whether you want to store all and output only records beginning with 'A' or only want to store records beginning with 'A' -- let me know if I need to make changes and I'm happy to help further.

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