Is there a way to make this error checking any better? Or is there something I am forgetting? I am expecting an integer then string. I added the suggestions to the code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
int main(void) {
// your code goes here
char line[150] = {0};
int sscanf_counter = 0;
int num = 0;
char string[150] = {0};
char dummy;
while(fgets(line, sizeof line, stdin))
{
printf("line is %s\n", line);
sscanf_counter = sscanf(line, "%d %s %c", &num, string, &dummy);
printf ("sscanf_counter: %d\n", sscanf_counter);
if (sscanf_counter == 2 && isalpha(string[0]))
{
printf ("Good value: %d\n", num);
printf ("string: <%s>\n", string);
}
else
{
printf ("BAD VALUE: %d \n", num);
printf ("string: <%s>\n", string);
}
memset(line, 0, sizeof line);
}
printf("Does this print? \n");
return 0;
}
I didn't want 222 to be converted to a string so I added a simple isalpha() check to my code. I want an actual number for my first value and actual alphabet characters not numbers converted to a string for the second value.
Output with small tweak:
aaa aaa
line is aaa aaa
sscanf_counter: 0
BAD VALUE: 0
string: <>
111 222
line is 111 222
sscanf_counter: 2
BAD VALUE: 111
string: <222>
111 aaa
line is 111 aaa
sscanf_counter: 2
Good value: 111
string: <aaa>
Is there a way to make this error checking any better?
"%d" is not specified on overflow. Stronger error checking can be provided with strtol().
"%d %s" does not detect extraneous extra input.
To deal with extra input and still use sscanf(), see below. If sscanf_counter == 3, extra non-white-space input detected.
char dummy;
sscanf_counter = sscanf(line, "%d %s %c", &num, string, &dummy);
The test if (sscanf_counter == ...) should happen before using the variables.
printf ("sscanf_counter: %d\n", sscanf_counter);
if (sscanf_counter >= 1) printf ("num: %d\n", num);
if (sscanf_counter >= 2) printf ("string: %s\n", string);
Tip: when printing a string, consider printable sententials to help detect leading/trailing white-space issues. (Even though these are not expected with "%s".)
// printf ("string: %s\n", string);
printf ("string: <%s>\n", string);
OT: Rather than code magic numbers, use code that adapts
// memset(line, 0, 150);
memset(line, 0, sizeof line);
// while(fgets(line, 150, stdin) != NULL)
while (fgets(line, sizeof line, stdin))
Related
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
void main (int argc, char* arcv[]) {
int fd, quest_num, i, j;
char* str;
char temp[2], buffer[20];
fd = open(arcv[1], O_WRONLY | O_CREAT, 0664);
printf("Insert number of questions\n");
scanf("%s", str);
write(fd, str, sizeof(str));
write(fd, "\n", 1);
quest_num = atoi(str);
for (i = 1; i <= quest_num; i++) {
printf("Insert question %d\n", i);
scanf("%[^\n]s", buffer);
printf("\n %s \n", buffer);
write(fd, "Question ", );
sprintf(temp, "%d", i);
write(fd, temp, sizeof(temp));
write(fd, "\n", 1);
write(fd, str, sizeof(temp));
write(fd, "\n", 1);
}
close(fd);
}
I want to make inputs like this:
Insert Number of Question:
2
Insert Question 1:
X+1=0 x=?
Insert Question 2:
X+y=0
and inside the file content i want it to look like this:
Question 1: X+1=0 x=?
1. 5
2. 2
3. 0
4. 1 Question 2: X+y=0
1. X=y
2. X= y
3. X=1
4. Y=1
but I get this in the terminal:
Insert number of questions
2
Insert question 1
x+y x=?
Insert question 2 (input is ignored here)
and inside the file:
2
Question 1
x+
Question 2
x=
Question 3
2
So in summary, scanf is ignoring the spaces input and there's an extra loop in the file content.
The basic problem is that when you scanf something that doesn't match a newline (either %s or %[^\n]) it won't read the newline at the end of the line and will leave it for the next stdio input rountine to read. You need to actually read and discard those newlines somehow.
The easiest way to do this is to use fgets to read input lines instead of scanf.
printf("Insert number of questions\n");
fgets(buffer, sizeof(buffer), stdin);
write(fd, buffer, strlen(buffer));
quest_num = atoi(buffer);
for (i = 1; i <= quest_num; i++) {
printf("Insert question %d\n", i);
fgets(buffer, sizeof(buffer), stdin);
Note that fgets will read the line including the newline, and the newline will be in the buffer, so if you don't want it, you'll need to strip it out explicitly.
If you really must use scanf, you can use a space in the format to explicitly skip over leading whitespace (including any lingering newline(s)), which might actually be desirable.
printf("Insert number of questions\n");
scanf("%19s", buffer);
write(fd, str, strlen(buffer));
write(fd, "\n", 1);
quest_num = atoi(buffer);
for (i = 1; i <= quest_num; i++) {
printf("Insert question %d\n", i);
scanf("% 19[^\n]", buffer);
printf("\n %s \n", buffer);
Note that when using scanf with %s or %[ and a fixed-size buffer, you should use an explicit size in the format to avoid buffer overruns
I need to read this main.txt file:
STUDENT ID 126345
TEACHER MATH CLASS 122
And i need to get each values from this file . If this main.txt file was like this:
STUDENT ID 126345
TEACHER ID 3654432
I could do it like this:
#include <stdio.h>
int main(){
FILE *ptr_file;
int id;
char string1[100],string2[100];
ptr_file = fopen("main.txt","r");
while(fscanf(ptr_file,"%s %s %d",string1,string2,id) != EOF){
}
}
But I can't use fscanf() function because space amount between these values is not equal for each line. How can i get every value in line by line like this code ?
If every line has 3 values separated by empty spaces (or tabs), then
fscanf(ptr_file,"%s %s %d",string1,string2,&id);
is OK (and note that I used &id instead of id), because %s matches a sequence of non-white-space characters, so it
doesn't matter how many empty spaces are between the values, %s will consume
the empty spaces:
#include <stdio.h>
int main(void)
{
const char *v = "STUDENT \t ID \t 126345";
int id;
char string1[100],string2[100];
sscanf(v, "%s %s %d", string1, string2, &id);
printf("'%s' '%s' %d\n", string1, string2, id);
return 0;
}
which prints
$ ./a
'STUDENT' 'ID' 126345
In general the most robost solution would be to read line by line with fgets
and parse the line with sscanf as you have more control over when a line has a
wrong format:
char buffer[1024];
while(fgets(buffer, sizeof buffer, stdin))
{
int id;
char string1[100],string2[100];
if(sscanf(buffer, "%99s %99s %d", string1, string2, &id) != 3)
{
fprintf(stderr, "Line with wrong format\n");
break;
}
// do something with read values
}
edit
User Weather Vane points out that the line might have 3 or 4 values, then you
could do this:
char buffer[1024];
while(fgets(buffer, sizeof buffer, stdin))
{
int id;
char string1[100],string2[100],string3[100];
res = sscanf(buffer, "%99s %99s %99s %d", string1, string2, string3, &id);
if(res != 4)
{
int res = sscanf(buffer, "%99s %99s %d", string1, string2, &id);
if(res != 3)
{
fprintf(stderr, "Line with wrong format, 3 or 4 values expected\n");
break;
}
}
if(res == 3)
printf("'%s' '%s' %d\n", string1, string2, id);
else
printf("'%s' '%s' '%s' %d\n", string1, string2, string3, id);
}
student.dat file
----------------
Stu:1 abc ($) - 55 in following order (Stu: %d %s (%c) - %d)
Stu:2 pqr (^) - 82
I am trying to read this file and save highest grade details in the variable in c programming.
my code is below but is not complete!
int main(){
int num, grade;
char id, name[35];
FILE *fp = NULL;
fp = fopen("student.dat", "r");
if (fp != NULL) {
while ((fp != '\n') && (fp != EOF)) {
fscanf(fp, "%d %s %c %d", &num, name, id, &grade);
printf("Student Num: %d", num);
printf("Student Name: %s", name);
printf("Student id: %c", id);
printf("Student grade: %d", grade);
}
fclose(fp);
}else {
printf("Failed to open file\n");
}
}
In C, you have 2 primary ways to read line-oriented input and then parse into individual values (really 3, but we will ignore walking a pair of pointers down the string for now).
The preferred manner is to use a line-oriented input function such as fgets or POSIX getline to read an entire line into a buffer, and then parse the buffer with sscanf which can be done in a more flexible manner than a single call to fscanf.
Nonetheless, you appear dedicated to using fscanf here. The key to using fscanf successfully is to provide a format string that accounts for all characters in the line to be read, or to craft the format string to take advantage of properties of the individual format specifiers to accomplish the same thing (e.g. %s (as well as your numerical conversions) will skip leading whitespace giving you some control to deal with line-endings that would otherwise be left in the input-buffer (either the file or stdin and therefore be the next character available on a subsequent call to fscanf, which if not properly handled, will throw a wrench into your read routine.
Another mandatory step is to validate that all conversions specified were successfully completed during each read. You do that by checking the return value for fscanf which is the match count (a count of the number of successful conversions that took place). If you do not check, you cannot have any type of confidence that your values actually hold the data you think they do.
Putting that together, using your input file, and taking the filename to open as the first argument to the program (and reading by default on stdin if no filename is given), you could do something like the following:
#include <stdio.h>
int main (int argc, char **argv) {
int num =0, grade = 0, max = 0; /* initialize all variables */
char id = 0, name[35];
const char *fmt = " Stu:%d %s (%c) - %d"; /* given format string */
FILE *fp = NULL;
if (!(fp = argc > 1 ? fopen (argv[1], "r") : stdin)) {
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
/* read each line and validate 4 successful conversions */
while (fscanf (fp, fmt, &num, name, &id, &grade) == 4) {
if (grade > max) max = grade;
printf ("Student Num: %d Name: %-12s id: %c grade: %d\n",
num, name, id, grade);
}
printf ("\n highest grade : %d\n\n", max);
if (fp != stdin) fclose (fp);
return 0;
}
Example Use/Output
$ ./bin/stdntread <dat/stdntread.dat
Student Num: 1 Name: abc id: $ grade: 55
Student Num: 2 Name: pqr id: ^ grade: 82
highest grade : 82
Look over the code, and especially the slight tweak to the format specifier, and let me know if you have any additional questions.
As user3386109 already hinted at: the format string "Stu: %d %s (%c) - %d" should do it. It actually doesn't, you need to add the newline, too.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
int num, grade, ret;
char id, name[35];
int lineno = 1;
FILE *fp = NULL;
// reset errno, just in case
errno = 0;
fp = fopen("student.dat", "r");
if (fp != NULL) {
for (;;) {
ret = fscanf(fp, "Stu: %d %s (%c) - %d\n", &num, name, &id, &grade);
if (ret != 4 && ret != EOF){
fprintf(stderr,"fscanf() returned %d instead of 4 for line %d\n",ret,lineno);
// unlikely, but cheap to check, so check
if(errno != 0){
fprintf(stderr,"With error %s\n",strerror(errno));
}
exit(EXIT_FAILURE);
}
if (ret == EOF) {
// fscanf() returns EOF for end-of-file _and_ error.
// check for error first
if(errno != 0){
fprintf(stderr,"The error %s occured while reading line %d\n",strerror(errno), lineno);
exit(EXIT_FAILURE);
}
// we are done with the file at this point and can bail out graciously
break;
}
printf("Student Num: %d, ", num);
printf("Student Name: %s, ", name);
printf("Student id: %c, ", id);
printf("Student grade: %d\n", grade);
lineno++;
}
fclose(fp);
} else {
printf("Failed to open file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
File student.dat generated with
for i in `seq 1 1 100`;do character=$(printf \\$(printf '%03o' $((`shuf -i 40-99 -n 1`))));name=$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w 3 | head -n 1); echo Stu:$i $name \($character\) - `shuf -i 10-99 -n 1`;done > student.dat
(Yes, that generation can be done simpler, I'm pretty sure ;-) )
First 10 lines of input (new-line is \n everywhere):
Stu:1 qim (+) - 13
Stu:2 EcF (L) - 61
Stu:3 Ko1 (Q) - 50
Stu:4 Ve7 (,) - 23
Stu:5 NiX (;) - 28
Stu:6 4O8 (C) - 73
Stu:7 00m (]) - 79
Stu:8 uiw (C) - 45
Stu:9 47k (X) - 80
Stu:10 MmJ (A) - 38
(file ends with new-line \n!)
Your while loop have incorrect condition, it'll never become false, File pointer never reaches to \n nor EOF, I had modified your code and now its working properly. Check while condition in code
int num, grade;
char id, name[35];
FILE *fp = NULL;
fp = fopen("student.dat", "r");
if (fp != NULL) {
int ret;
while((ret = fscanf(fp, "%d %s %c %d", &num, name, &id, &grade))!=EOF)
{ printf(" Student Num: %d", num);
printf(" Student Name: %s", name);
printf(" Student id: %c", id);
printf(" Student grade: %d\n", grade);
}
fclose(fp);
}else {
printf("Failed to open file\n");
}
I'm basically writing the code to reading things that store the rest of the string if it starts with an l. Here is my code so far:
char input[80];
char fileName[80];
fgets(input, 80, stdin); //Need to use because only want to read maximum 80 characters
if(input[0] == 'l') {
printf("String length: %d\n", strlen(input));
printf("String input: %s", input);
strncpy(fileName, &input[1], (strlen(input)) -2);
fileName[strlen(input)-1] = '\0';
printf("Filename to save: %s \n", fileName);
}
When I input ljudyjudyjudyjudy
the filename I get when I printf is judyjudyjudyjudyH
It works sometimes with different inputs, but sometimes extra characters prop up?
I think you are off by one:
fgets(input, 80, stdin); //Need to use because only want to read maximum 80 characters
if(input[0] == 'l') {
printf("String length: %d\n", strlen(input));
printf("String input: %s", input);
strncpy(fileName, &input[1], (strlen(input)) -2);
fileName[strlen(input)-2] = '\0'; // should be -2 instead
printf("Filename to save: %s \n", fileName);
}
In your example with "ljudyjudyjudyjudy" as input, you want to set fileName[16] to '\0' rather than fileName[17].
I am trying to read in a variable length user input and perform some operation (like searching for a sub string within a string).
The issue is that I am not aware how large my strings (it is quite possible that the text can be 3000-4000 characters) can be.
I am attaching the sample code which I have tried and the output:
char t[],p[];
int main(int argc, char** argv) {
fflush(stdin);
printf(" enter a string\n");
scanf("%s",t);
printf(" enter a pattern\n");
scanf("%s",p);
int m=strlen(t);
int n =strlen(p);
printf(" text is %s %d pattrn is %s %d \n",t,m,p,n);
return (EXIT_SUCCESS);
}
and the output is :
enter a string
bhavya
enter a pattern
av
text is bav 3 pattrn is av 2
Please don't ever use unsafe things like scanf("%s") or my personal non-favourite, gets() - there's no way to prevent buffer overflows for things like that.
You can use a safer input method such as:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
You can then set the maximum size and it will detect if too much data has been entered on the line, flushing the rest of the line as well so it doesn't affect your next input operation.
You can test it with something like:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
In practice you shouldn't bother too much to be precise. Give yourself some slack to have some memory on the stack and operate on this. Once you want to pass the data further, you can use strdup(buffer) and have it on the heap. Know your limits. :-)
int main(int argc, char** argv) {
char text[4096];
char pattern[4096];
fflush(stdin);
printf(" enter a string\n");
fgets(text, sizeof(text), stdin);
printf(" enter a pattern\n");
fgets(pattern, sizeof(pattern), stdin);
int m=strlen(text);
int n =strlen(pattern);
printf(" text is %s %d pattrn is %s %d \n",text,m,pattern,n);
return (EXIT_SUCCESS);
}
Don't use scanf or gets for that matter because as you say, there is not real way of knowing just how long the input is going to be. Rather use fgets using stdin as the last parameter. fgets allows you to specify the maximum number of characters that should be read. You can always go back and read more if you need to.
scanf(%s) and gets read until they find a terminating character and may well exceed the length of your buffer causing some hard to fix problems.
The main problem in your case is having char arrays of unknown size. Just specify the array size on declaration.
int main(int argc, char** argv) {
int s1[4096], s2[4096];
fflush(stdin);
printf(" enter a string\n");
scanf("%s", s1);
printf(" enter a pattern\n");
scanf("%s", s2);
int m = strlen(s1);
int n = strlen(s2);
printf(" text is %s of length %d, pattern is %s of length %d \n", s1, m, s2, n);
return (EXIT_SUCCESS);
}