I have a text file that has 200 x 150 data in it (every number is separated with a space). I am trying to store these numbers into a matrix. My code is this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int i,j, matrix[200][150];
void getMatrix(){
FILE *file;
file = fopen("PICTURE.txt", "r");
char *line, *number;
j = 0;
while(!feof(file)){
i = 0;
fscanf(file, "%s", line);
number = strtok(NULL, " ");
while(number!= NULL){
matrix[i][j] = atoi(number);
}
printf("\n");
j++;
}
}
void printMatrix(){
int a, b;
for(a=0; a<i; a++){
for(b=0; b<j; b++){
printf("%d", matrix[a][b]);
}
printf("\n");
}
}
int main(){
getMatrix();
printMatrix();
}
It prints out literally nothing. I don't understand why. How can I fix my code or do you have any other suggestions for rewriting it?
if you have a file, with 200x150 integer values (either on 200 lines, or all values on one line), then simply looping though the file with fscanf() reading and storing an integer at a time is all that is needed. You must validate each read and provide a way to communicate any failure within a function through the function return (or though a pointer updated within the function)
To begin, do not use MagicNumbers in your code and do not hardcode filenames. Instead, #define the constants you need (or use a global enum) and pass the open FILE* pointer to your read-function as a parameter after having opened (and validated that the file is open for reading) in the calling function. (If the file open fails -- there is no reason to make the read-function call to begin with)
In your case, you only have two needed constants:
#define ROWS 200 /* if you need a constant, #define one (or more) */
#define COLS 150
Do not use global variables unless absolutely necessary (there will be virtually no case where globals are needed when you begin learning C). Passing the matrix (2D array) as a parameter to your read-function, it can be written as:
/* fill m[ROWS][COLS] with values read from fp.
* returns 1 on success, 0 otherwise.
*/
int getmatrix (int (*m)[COLS], FILE *fp)
{
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
if (fscanf (fp, "%d", &m[row][col]) != 1) {
fprintf (stderr, "error reading m[%d][%d]\n", row, col);
return 0;
}
}
}
return 1;
}
(note: how the return type for the function is int where 1 (true) is returned on success and 0 (false) is returned on failure. Also note that if 200x150 values are unable to be read, failure is returned)
Pass the filename to read from as the 1st argument to your program (that's what int argc, char **argv parameters to main() are for). Alternatively, you can prompt the user and read the filename as input. You can provide a fixed filename as a default value if no filename is entered (stdin is used as the default below), but otherwise do not hardcode filenames. You should not have to recompile your program simply to read from a different filename.
With those improvements made, your main() could be:
int main (int argc, char **argv) {
int matrix[ROWS][COLS] = {{0}}; /* do not use global variables */
/* 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 in caller */
perror ("file open failed");
return 1;
}
if (!getmatrix (matrix, fp)) { /* pass array and open FILE*, validate */
return 1;
}
fclose (fp);
prnmatrix (matrix); /* output results */
}
The complete program is:
#include <stdio.h>
#define ROWS 200 /* if you need a constant, #define one (or more) */
#define COLS 150
/* fill m[ROWS][COLS] with values read from fp.
* returns 1 on success, 0 otherwise.
*/
int getmatrix (int (*m)[COLS], FILE *fp)
{
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
if (fscanf (fp, "%d", &m[row][col]) != 1) {
fprintf (stderr, "error reading m[%d][%d]\n", row, col);
return 0;
}
}
}
return 1;
}
void prnmatrix (int (*m)[COLS])
{
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
printf (col ? " %4d" : "%4d", m[row][col]);
}
putchar ('\n');
}
}
int main (int argc, char **argv) {
int matrix[ROWS][COLS] = {{0}}; /* do not use global variables */
/* 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 in caller */
perror ("file open failed");
return 1;
}
if (!getmatrix (matrix, fp)) { /* pass array and open FILE*, validate */
return 1;
}
fclose (fp);
prnmatrix (matrix); /* output results */
}
Example Use/Output
With 200 x 150 random values less than 10000 generated and written to dat/int-200x150.txt, you would run the program as:
$ ./bin/read2darrint dat/int-200x150.txt
9240 5939 3063 5789 7401 7869 4363 3321 7788 1008 7850 ...
2760 5263 5338 6390 8146 155 324 916 4489 3718 1616 ...
...
Where the output is 200 lines of 150 numbers each.
There are many ways to do this. One (better) way would be to loop reading each line of integer values as a string with fgets() and converting each set of ASCII digits into an integer value with strtol(). I say "better" in a sense that strtol() provides far better error reporting in the case of a failed conversion that the simple pass/fail that can be obtained from fscanf().
The read before looping with strtol() would be with fgets() into a sufficiently sized buffer. That way if a matching-failure occurs, the error only impacts that line of data and no offending data is left in your input stream unread (as will occur with a matching-failure and fscanf().
That said, with a properly formatted space-separated file full of data, there is nothing wrong with using fscanf() directly, just be aware there are more robust ways to do the read and conversion.
To use strtol(), to work through each line of values, you simply use a pointer to the beginning of each set of digits calling strtol() which then updates the endptr to one past the last digit converted allowing you to work your way though each number by updating the pointer (nptr) to endptr after each validated conversion. See man 3 strtol. You could updated the code above with:
...
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
...
#define MAXC 2048 /* (150 int + space) max of 1800 chars per-line */
and finally change your function to use strtol(), e.g.
/* fill m[ROWS][COLS] with values read from fp.
* returns 1 on success, 0 otherwise.
*/
int getmatrix (int (*m)[COLS], FILE *fp)
{
char buf[MAXC]; /* buffer to hold each line */
for (int row = 0; row < ROWS; row++) {
char *nptr = buf, *endptr = nptr; /* nptr and endptr for strtol */
if (!fgets (buf, MAXC, fp)) { /* read line of input */
fprintf (stderr, "error: failed to read line %d\n", row);
return 0;
}
for (int col = 0; col < COLS; col++) {
errno = 0; /* reset errno */
long tmp = strtol (nptr, &endptr, 10); /* convert w/strtol */
if (nptr == endptr) { /* no ASCII digits coverted to a number */
fputs ("error: no digits converted.\n", stderr);
return 0;
}
else if (errno) { /* error in converstion, under/overflow */
fputs ("error: under/overflow occurred in conversion.\n", stderr);
return 0;
}
/* conversion outside range of int */
else if (tmp < INT_MIN || INT_MAX < tmp) {
fputs ("error: conversion out of range of integer.\n", stderr);
return 0;
}
m[row][col] = tmp; /* assign tmp to m[row][col] */
nptr = endptr; /* update nptr to endptr */
}
}
return 1;
}
If an error occurs, you will know exactly what caused the failure. Look things over and let me know if you have further questions.
Many problems:
Never use while(!feof(file)){. Instead check the return value of fscanf(file, ...)
Do not use "%s" with out a width limit.
Do not use pass line to a function without initializing/assigning it first.
i never incremented.
fclose(file) when done.
Check success of fopen(...);
GTG, maybe more later.
Related
I have a file input.dat. In this file, there are 3 lines:
1 2 3
5 7 10 12
8 9 14 13 15 17
I am going to read one of the three lines using C, and return the number of the elements.
For example, I want to read the 2nd line 5 7 10 12 into memory, and also return the number of values in the 2nd line, which is 4. My code is below...
#include <stdio.h>
#include <stdlib.h>
#define STRING_SIZE 2000
int main() {
FILE *fp = fopen("in.dat", "r");
char line[STRING_SIZE];
int lcount = 0, nline = 1, sum = 0, number;
if (fp != NULL) {
while (fgets(line, STRING_SIZE, fp) != NULL) {
if (lcount == nline) {
while (sscanf(line, "%d ", &number)) {
sum++;
}
break;
} else {
lcount++;
}
}
fclose(fp);
}
exit(0);
}
When I run this code, it never stops like a dead loop. What is the problem here?
the loop while (sscanf(line, "%d ", &number)) keeps parsing the first number in the line.
You should use strtol instead:
#include <stdio.h>
#include <stdlib.h>
#define STRING_SIZE 2000
int main() {
FILE *fp = fopen("in.dat", "r");
char line[STRING_SIZE];
int lcount = 0, nline = 1;
if (fp != NULL) {
while (fgets(line, STRING_SIZE, fp) != NULL) {
if (lcount == nline) {
char *p = line, *q;
int count = 0;
for (;;) {
long val = strtol(p, &q, 0); // parse an integer
if (q == p) {
// end of string or not a number
break;
}
// value was read into val. You can use it for whatever purpose
count++;
p = q;
}
printf("%d\n", count);
break;
} else {
lcount++;
}
}
fclose(fp);
}
return 0;
}
You were thinking along the right path with your use of sscanf(), the only piece of the puzzle you were missing is how to apply an offset to line so that you read the next value in the line with your next call to sscanf(). You do that by keeping track of the number of characters consumed on each call to sscanf() using the "%n" conversion (it does not add to your conversion count returned by sscanf()) For example reading lines from the open file-stream fp, you could do:
#define MAXC 1024 /* if you need a constant, #define one (or more) */
...
char line[MAXC] = ""; /* buffer to hold each line */
...
while (fgets (line, MAXC, fp)) { /* reach each line in file */
int offset = 0, /* offset in line for next sscanf() read */
nchr = 0, /* number of char consumed by last read */
val, /* integer value read with sscanf() */
nval = 0; /* number of values read in line */
/* conververt each integer at line + offset, saving no. of chars consumed */
while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
printf (" %d", val); /* output value read */
offset += nchr; /* update offset with no. chars consumend */
nval++; /* increment value count */
}
printf (" - %d values\n", nval); /* output no. values in line */
}
(Note: strtol() provides better error reporting than sscanf() on a failed conversion)
If you put it together with an example that reads from the filename provided as the first argument to the program (or reads from stdin by default if no argument is given), you could do:
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char line[MAXC] = ""; /* buffer to hold each line */
/* 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;
}
while (fgets (line, MAXC, fp)) { /* reach each line in file */
int offset = 0, /* offset in line for next sscanf() read */
nchr = 0, /* number of char consumed by last read */
val, /* integer value read with sscanf() */
nval = 0; /* number of values read in line */
/* conververt each integer at line + offset, saving no. of chars consumed */
while (sscanf (line + offset, "%d%n", &val, &nchr) == 1) {
printf (" %d", val); /* output value read */
offset += nchr; /* update offset with no. chars consumend */
nval++; /* increment value count */
}
printf (" - %d values\n", nval); /* output no. values in line */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
}
Example Use/Output
With the data you show in the filename dat/nvals.txt you would get:
$ ./bin/fgetsnvals dat/nvals.txt
1 2 3 - 3 values
5 7 10 12 - 4 values
8 9 14 13 15 17 - 6 values
Look things over and let me know if you have further questions.
A bit cleaner version of chqrlie's answer. Started with a string as that is what the question is really about after fgets().
sscanf() won't step through the string, it always reads from the beginning.
strtol() looks for a long int in the beginning of the string, ignoring initial white-space. Gives back the address of where it stops scanning.
The manual of strtol() says that errno should be checked for any conversion error.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define STRING_SIZE 2000
int main(void)
{
char line[STRING_SIZE] = "5 7 10 12";
char* start = line;
char* end;
int count = 0;
while(1)
{
/**
* strtol() look for long int in beginning of the string
* Ignores beginning whitespace
*
* start: where to strtol() start looking for long int
* end: where strtol() stops scanning for long int
*/
errno = 0; // As strol() manual says
strtol(start, &end, 0);
if (errno != 0)
{
printf("Error in strtol() conversion.\n");
exit(0);
}
if (start == end) break; // Quit loop
start = end;
count++;
}
printf("count: %d\n", count);
return 0;
}
I have a .txt file that contains data in this format:
xxxx: 0.9467,
yyyy: 0.9489,
zzzz: 0.78973,
hhhh: 0.8874,
yyyy: 0.64351,
xxxx: 0.8743,
and so on...
Let's say that my C program receives, as input, the string yyyy. The program should, simply, return all the instances of yyyy in the .txt file and the average of all their numerical values.
int main() {
FILE *filePTR;
char fileRow[100000];
if (fopen_s(&filePTR, "file.txt", "r") == 0) {
while (fgets(fileRow, sizeof fileRow, filePTR) != NULL) {
if (strstr(fileRow, "yyyy") != NULL) { // Input parameter
printf("%s", fileRow);
}
}
fclose(filePTR);
printf("\nEnd of the file.\n");
} else {
printf("ERROR! Impossible to read the file.");
}
return 0;
}
This is my code right now. I don't know how to:
Isolate the numerical values
actually convert them to double type
average them
I read something about the strtok function (just to start), but I would need some help...
You have started off on the right track and should be commended for using fgets() to read a complete line from the file on each iteration, but your choice of strstr does not ensure the prefix you are looking for is found at the beginning of the line.
Further, you want to avoid hardcoding your search string as well as the file to open. main() takes arguments through argc and argv that let you pass information into your program on startup. See: C11 Standard - ยง5.1.2.2.1 Program startup(p1). Using the parameters eliminates your need to hardcode values by letting you pass the filename to open and the prefix to search for as arguments to your program. (which also eliminates the need to recompile your code simply to read from another filename or search for another string)
For example, instead of hardcoding values, you can use the parameters to main() to open any file and search for any prefix simply using something similar to:
#include <stdio.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char buf[MAXC] = "", *str = NULL; /* buffer for line and ptr to search str */
size_t n = 0, len = 0; /* counter and search string length */
double sum = 0; /* sum of matching lines */
FILE *fp = NULL; /* file pointer */
if (argc < 3) { /* validate 2 arguments given - filename, search_string */
fprintf (stderr, "error: insufficient number of arguments\n"
"usage: %s filename search_string\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for reading */
perror ("fopen-filename");
return 1;
}
str = argv[2]; /* set pointer to search string */
len = strlen (str); /* get length of search string */
...
At this point in your program, you have opened the file passed as the first argument and have validated that it is open for reading through the file-stream pointer fp. You have passed in the prefix to search for as the second argument, assigned it to the pointer str and have obtained the length of the prefix and have stored in in len.
Next you want to read each line from your file into buf, but instead of attempting to match the prefix with strstr(), you can use strncmp() with len to compare the beginning of the line read from your file. If the prefix is found, you can then use sscanf to parse the double value from the file and add it to sum and increment the number of values stored in n, e.g.
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
if (strncmp (buf, str, len) == 0) { /* if prefix matches */
double tmp; /* temporary double for parse */
/* parse with scanf, discarding prefix with assignment suppression */
if (sscanf (buf, "%*1023[^:]: %lf", &tmp) == 1) {
sum += tmp; /* add value to sum */
n++; /* increment count of values */
}
}
}
(note: above the assignment suppression operator for sscanf(), '*' allows you to read and discard the prefix and ':' without having to store the prefix in a second string)
All that remains is checking if values are contained in sum by checking your count n and if so, output the average for the prefix. Or, if n == 0 the prefix was not found in the file, e.g.:
if (n) /* if values found, output average */
printf ("prefix '%s' avg: %.4f\n", str, sum / n);
else /* output not found */
printf ("prefix '%s' -- not found in file.\n", str);
}
That is basically all you need. With it, you can read from any file you like and search for any prefix simply passing the filename and prefix as the first two arguments to your program. The complete example would be:
#include <stdio.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char buf[MAXC] = "", *str = NULL; /* buffer for line and ptr to search str */
size_t n = 0, len = 0; /* counter and search string length */
double sum = 0; /* sum of matching lines */
FILE *fp = NULL; /* file pointer */
if (argc < 3) { /* validate 2 arguments given - filename, search_string */
fprintf (stderr, "error: insufficient number of arguments\n"
"usage: %s filename search_string\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for reading */
perror ("fopen-filename");
return 1;
}
str = argv[2]; /* set pointer to search string */
len = strlen (str); /* get length of search string */
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
if (strncmp (buf, str, len) == 0) { /* if prefix matches */
double tmp; /* temporary double for parse */
/* parse with scanf, discarding prefix with assignment suppression */
if (sscanf (buf, "%*1023[^:]: %lf", &tmp) == 1) {
sum += tmp; /* add value to sum */
n++; /* increment count of values */
}
}
}
if (n) /* if values found, output average */
printf ("prefix '%s' avg: %.4f\n", str, sum / n);
else /* output not found */
printf ("prefix '%s' -- not found in file.\n", str);
}
Example Use/Output
Using your data file stored in dat/prefixdouble.txt, you can search for each prefix in the file and obtain the average, e.g.
$ ./bin/prefixaverage dat/prefixdouble.txt hhhh
prefix 'hhhh' avg: 0.8874
$ ./bin/prefixaverage dat/prefixdouble.txt xxxx
prefix 'xxxx' avg: 0.9105
$ ./bin/prefixaverage dat/prefixdouble.txt yyyy
prefix 'yyyy' avg: 0.7962
$ ./bin/prefixaverage dat/prefixdouble.txt zzzz
prefix 'zzzz' avg: 0.7897
$ ./bin/prefixaverage dat/prefixdouble.txt foo
prefix 'foo' -- not found in file.
Much easier than having to recompile each time you want to search for another prefix. Look things over and let me know if you have further questions.
My .txt looks like this for example:
1.0 2.0
1.0 1.0
2.0 3.0
2.0 1.0
I want to run through this file using each line when its needed. To explain I have an equation (L = Ax + B) which I need to calculate for different x's. Is there a way to read each line in the file seperately, taking A and B at that line. So the first term of L will use the first A and B.
I haven't done this before. This is what I know already:
void coefficients(double *A, double *B) {
FILE *infile;
if(2!=fscanf(infile,"%lf %lf",A, B)) {
printf("Error reading parameters from file\n");
exit(1);
}
fclose(infile);
}
And then calling this in int_main:
double A;
double B;
// Read in from file;
coefficients(&A, &B);
You can use this method to read a text file(file.txt) to a char array (buffer) line by line by using fgets() then parse it with sscanf() to find double value in it.
Code
double a=0;
double b=0;
int bufferLength = 1024;
char buffer[bufferLength];
FILE *fp = fopen("file.txt", "r");
if (!fp){
printf("Cant open file\n");
return -1;
}
while(fgets(buffer, bufferLength, fp)) {
//printf("%s\n", buffer);
if (2==sscanf(buffer, "%lf %lf", &a,&b)){
printf("a: %f b: %f\n", a,b);
}
}
fclose(fp);
With a file with potential blank lines and potential lines that contain something other than the pair of double values you are looking for, you are much better off reading each line into a buffer and the parsing the wanted values from the buffer with sscanf rather than attempting to read directly with fscanf. This way a line of input is consumed on each read and you don't have to worry about what remains in your input stream depending on the conversion specifiers used.
Further, in order to read multiple values into A and B, both A and B must be arrays of double (or an allocated block of memory that can hold multiple double values)
For example in main() can declare:
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
...
int main (int argc, char **argv) {
double a[MAXC] = {0}, b[MAXC] = {0}; /* arrays of MAXC doubles */
Now you have storage for 1024 double values. Your coefficient function that reads the double values should return the number of values read. It should also take the open and validated FILE* stream as a parameter. (if the file cannot be opened in validated in the caller -- there is no reason to make the function call to attempt the read to begin with)
Putting that together you could do:
size_t coefficients (FILE *fp, double *a, double *b)
{
char buf[MAXC]; /* buffer for reading each line */
size_t ncoeff = 0; /* number of coefficient pairs read */
while (ncoeff < MAXC && fgets (buf, MAXC, fp)) /* read each line */
/* if it contains 2 double values */
if (sscanf (buf, "%lf %lf", &a[ncoeff], &b[ncoeff]) == 2)
ncoeff++; /* increment counter */
return ncoeff; /* return total count of pairs read */
}
A short example putting it altogether, you would have:
#include <stdio.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
size_t coefficients (FILE *fp, double *a, double *b)
{
char buf[MAXC]; /* buffer for reading each line */
size_t ncoeff = 0; /* number of coefficient pairs read */
while (ncoeff < MAXC && fgets (buf, MAXC, fp)) /* read each line */
/* if it contains 2 double values */
if (sscanf (buf, "%lf %lf", &a[ncoeff], &b[ncoeff]) == 2)
ncoeff++; /* increment counter */
return ncoeff; /* return total count of pairs read */
}
int main (int argc, char **argv) {
double a[MAXC] = {0}, b[MAXC] = {0}; /* arrays of MAXC doubles */
size_t n = 0; /* count of doubles returned */
/* 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;
}
if (!(n = coefficients (fp, a, b))) { /* validate coeff pairs read */
fputs ("error: no double values read from file.\n", stderr);
return 1;
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (size_t i = 0; i < n; i++) /* output coefficients */
printf ("a[%zu] : %.1f b[%zu] : %.1f\n", i, a[i], i, b[i]);
}
(you can adjust the size of the arrays as needed)
Example Input File
$ cat dat/coeffpairs.txt
A B
1.0 2.0
1.0 1.0
2.0 3.0
2.0 1.0
Example Use/Output
$ ./bin/readcoeff dat/coeffpairs.txt
a[0] : 1.0 b[0] : 2.0
a[1] : 1.0 b[1] : 1.0
a[2] : 2.0 b[2] : 3.0
a[3] : 2.0 b[3] : 1.0
Look things over and let me know if you have further questions.
It depends on what you want to do. If you just want to read the data and don't care what happens when it is not in the form of two columns, you can just read it with fscanf in a loop:
double A, B;
while(fscanf(fp, "%ld%ld", &A, &B) == 2) {
// do something with A and B
This will read any sequence of numbers in the file -- maybe in two columns or all on one line or randomly split between lines with blank lines, and will stop when it gets to anything that is not a number (or the end of the file, and will ignore any odd single number at the end).
If on the other hand you want to verify that the numbers are two per line and no blank lines, you can use fgets+sscanf:
char line[256];
while (fgets(line, sizeof line, fp)) {
double A, B;
int end;
if (sscanf(line, "%ld%ld %n", &A, &B, &len) == 2 && line[end] == 0) {
// got a line with (just) a pair of numbers
} else {
fprintf(stderr, "malformed input line: %s", line);
}
}
If you also want to check that the numbers are lined up in columns, that is a bit trickier. You can check for left- or right-aligned columns pretty easily
char line[256];
int a_col = -1, b_col = -1, lineno = 0;
while (fgets(line, sizeof line, fp)) {
double A, B;
int a_right, b_right, end;
++lineno;
if (sscanf(line, "%ld%n%ld%n %n", &A, &a_right, &B, &b_right, &len) == 2 && line[end] == 0) {
// got a line with (just) a pair of numbers
if (a_col >= 0 && (a_col != a_right || b_col != b_right)) {
fprintf(stderr, "columns on line %d not lined up with previous line\n", lineno);
}
a_col = a_right;
b_col = b_right;
} else {
fprintf(stderr, "malformed input on line %d", lineno);
}
}
my teacher asked me to make a program that can read employee's ID, name and salary from txt file, but I'm currently struggling with writing code that can read the txt in order.
#include<stdio.h>
typedef struct em{
char id[20];
char name[256];
float sal;
};
void read_file(em *a, int *i){
FILE *p;
*i=0;
char x[20],y[20],z[20];
bool isRead;
p=fopen("D:/--LMS--/PRF192/Assignment_File/A.txt","r");
if (p==NULL){
printf("File not found...");
} else {
fscanf(p,"%s %s %s",x,y,z);
while (fscanf(p,"%s %s %f",a[*i].id,a[*i].name,&a[*i].sal)!=EOF){
*i++; }
for (int t=1;t<=*i;t++){
printf("%s\n%s\n%f\n",a[*i].id,a[*i].name,a[*i].sal);
}
} printf("Finished");
}
main(){
em em[100];
int amount=0;
read_file(em,&amount);
}
It should output the file's data along with "Finished" as the final line.
File Data:
EmID EmName EmsalaryLevel
A001 EgsyPrub 3.4
A002 PattyEin 2.4
A003 TheodoreEly 4.5
While you have discovered the problem with *i++ incrementing the pointer-address instead of the value at the address held by the pointer (*i)++, you would benefit from simplifying things by separating what you need to accomplish into distinct operations.
First, don't use magic numbers in your code (except where absolutely required like with the scanf field-width modifier) and don't use hardcoded strings or filenames. Instead, If you need a constant, #define one (or more), or use a global enum to do the same thing. That way you have one single place at the top of your code to change things if needed and you don't have to go picking through your declarations or loop limits to change things.
In your case, you can simply #define the needed constants, and you pass the filename to read as an argument to your program (that's what your argument-count and argument-vector are for, e.g. int argc, char **argv) For example:
#include <stdio.h>
#define MAXID 20 /* don't use magic numbers in your code! */
#define MAXEM 100 /* if you need a constant, #define one (or more) */
#define MAXNM 256
#define MAXC 1024
...
typedef struct { /* struct for employee data */
char id[MAXID];
char name[MAXNM];
float sal;
} employee; /* (note: typedef name goes at end...) */
Now, looking at your data-file, you need to read two different types of lines of data. (1) a heading line with 3-categories that you call x, y, z (not descriptive, but we will run with it...), and (2) employee data consisting of and id, name, sal which you are currently storing in an array of struct (that's good!).
While the heading line could probably be a single line without being broken into x, y, z, you need some way to get that data back from your function. A simple way is to create a second typedef for a struct to hold x, y & z and you can make that available back in the caller the same way you do a (your array of employee data), e.g.
typedef struct { /* provide a struct for heading values */
char x[MAXID],y[MAXID],z[MAXID];
} heading;
You are performing Input Critical operations in read_file yet your return type is void? If there is any chance of failure in your function, you need to choose a meaningful return type that can indicate success/failure. While you are at it, why not return the number of employee records read instead of passing the pointer to i?
That way you can indicate failure with a return 0; and indicate success by returning the positive number of records read -- which can be assigned to amount back in the caller eliminating the need to monkey with incrementing the value held by the pointer i altogether. (and since it will be a count, a proper type would be size_t rather than int -- you won't have a negative count)
As mentioned in the comment, you generally want to attempt to open the file in the caller and pass an open FILE* pointer to your function as a parameter. That allows you to validate you have an open file before calling your read_file function. No need for the function call, if the file doesn't exist. Adding those considerations, you could write your read_file function as:
/* read heading and employee data from open file fp, returning
* the number of employee records on success, 0 on failure.
*/
size_t read_file (FILE *fp, heading *h, employee *a)
{
char buf[MAXC]; /* buffer to read each line */
size_t i = 0; /* employee record counter (not including heading) */
if (fgets (buf, MAXC, fp)) { /* read heading & validate */
if (sscanf (buf, "%19s %19s %19s", h->x, h->y, h->z) != 3) {
fputs ("error: invalid 1st line format.\n", stderr);
return 0;
}
}
while (i < MAXEM && fgets (buf, MAXC, fp)) { /* read each line */
if (sscanf (buf, "%19s %19s %f",
a[i].id, a[i].name, &a[i].sal) != 3) {
fprintf (stderr, "invalid format line %zu.\n", i);
return i;
}
i++; /* increment count only on successful parse */
}
return i; /* return number of employee records */
}
(note: how all line of data are read with fgets into buf before the individual values are parsed from each line. This ensures you consume an entire line of data with each input call. Note also, how the array bounds of each array are protect by using the field-width modifier to each conversion specifier used in sscanf and by including i < MAXEM in your read loop)
Opening your file in main(), passing the open file pointer along with a pointer to a heading struct and your array of employee data and assigning the return to amount allows you to call your function similar to:
if ((amount = read_file (fp, &h, em))) { /* validate records read */
(you can of course, do amount = read_file (fp, &h, em); first and then validate with if (amount) { -- it's up to you)
A complete main() that takes the filename to read as its argument (or reads from stdin by default if no argument is given) could be written as follows:
int main (int argc, char **argv) {
heading h = { .x = "" }; /* declare heading struct */
employee em[MAXEM] = {{ .id = "" }}; /* declare array of employees */
size_t amount = 0; /* counting types use size_t */
/* 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;
}
if ((amount = read_file (fp, &h, em))) { /* validate records read */
printf ("%-8s%-12s%s\n", h.x, h.y, h.z); /* output heading */
for (size_t i = 0; i < amount; i++) /* output employee recs */
printf ("%-8s%-12s%.1f\n", em[i].id, em[i].name, em[i].sal);
}
else /* otherwise throw error that read failed */
fputs ("error: no data read from file.\n", stderr);
}
(the formatting is handled by the field-width modifier again used with print to preserve your 8-character and 12-character field width for columns 1 & 2 from your data file.)
Putting it altogether you could do:
#include <stdio.h>
#define MAXID 20 /* don't use magic numbers in your code! */
#define MAXEM 100 /* if you need a constant, #define one (or more) */
#define MAXNM 256
#define MAXC 1024
typedef struct { /* provide a struct for heading values */
char x[MAXID],y[MAXID],z[MAXID];
} heading;
typedef struct { /* struct for employee data */
char id[MAXID];
char name[MAXNM];
float sal;
} employee; /* (note: typedef name goes at end...) */
/* read heading and employee data from open file fp, returning
* the number of employee records on success, 0 on failure.
*/
size_t read_file (FILE *fp, heading *h, employee *a)
{
char buf[MAXC]; /* buffer to read each line */
size_t i = 0; /* employee record counter (not including heading) */
if (fgets (buf, MAXC, fp)) { /* read heading & validate */
if (sscanf (buf, "%19s %19s %19s", h->x, h->y, h->z) != 3) {
fputs ("error: invalid 1st line format.\n", stderr);
return 0;
}
}
while (i < MAXEM && fgets (buf, MAXC, fp)) { /* read each line */
if (sscanf (buf, "%19s %19s %f",
a[i].id, a[i].name, &a[i].sal) != 3) {
fprintf (stderr, "invalid format line %zu.\n", i);
return i;
}
i++; /* increment count only on successful parse */
}
return i; /* return number of employee records */
}
int main (int argc, char **argv) {
heading h = { .x = "" }; /* declare heading struct */
employee em[MAXEM] = {{ .id = "" }}; /* declare array of employees */
size_t amount = 0; /* counting types use size_t */
/* 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;
}
if ((amount = read_file (fp, &h, em))) { /* validate records read */
printf ("%-8s%-12s%s\n", h.x, h.y, h.z); /* output heading */
for (size_t i = 0; i < amount; i++) /* output employee recs */
printf ("%-8s%-12s%.1f\n", em[i].id, em[i].name, em[i].sal);
}
else /* otherwise throw error that read failed */
fputs ("error: no data read from file.\n", stderr);
}
Compiling
With gcc:
gcc -Wall -Wextra -pedantic -Wshadow -std=c11 -O2 -o idnamesal idnamesal.c
With VS Developer's Command Prompt
cl /W3 /wd4996 /Ox /Feidnamesal /Tc idnamesal.c
Example Use/Output
Which when compiled and provided your input file as an argument, would produce the following output:
$ ./bin/idnamesal dat/emdata.txt
EmID EmName EmsalaryLevel
A001 EgsyPrub 3.4
A002 PattyEin 2.4
A003 TheodoreEly 4.5
(output on both Linux and Windows is the same)
Lastly, always compile with warnings enabled, and do not accept code until it compiles cleanly without warning. To enable warnings add -Wall -Wextra -pedantic to your gcc/clang compile string (also consider adding -Wshadow to warn on shadowed variables). For VS (cl.exe on windows), use /W3. Read and understand each warning -- then go fix it. They will identify any potential problems, and the exact line on which it occurs. You can learn a lot by listening to what your compiler is telling you.
There are many ways to put this read together, so don't take this as the only way. The key, regardless how you do it, is the validate every critical step in your program and always protect your array bounds from overrun. Doing that alone will save you no end of grief as you learn C.
There are two main issues that cause the code not to work properly:
Inside the first loop that reads from the file, you try to increase the value pointed to by i. However, you actually increase the value of i itself and then try to dereference it, which causes invalid memory access. In order to solve it, surround the dereferencing operation with parenthesis, so that it will be executed first:
(*i)++;
You forgot to use your new index variable, t, in the second loop for printing the data. Instead you just use the same index value (*i) for all of the iterations. You should also start your count from 0, same as the initial value of *i.
for (int t=0;t<*i;t++){
printf("%s\n%s\n%f\n",a[t].id,a[t].name,a[t].sal);
}
I'm kind of new to C.
I need to write a small function that opens a configuration file that has 3 lines, each line contains a path to files/directories that I need to extract.
I wrote this program and it seem to work:
void readCMDFile(char* cmdFile,char directoryPath[INPUT_SIZE], char inputFilePath[INPUT_SIZE],char outputFilePath [INPUT_SIZE]) {
//open files
int file = open(cmdFile, O_RDONLY);
if (file < 0) {
handleFailure();
}
char buffer[BUFF_SIZE];
int status;
int count;
while((count=read(file,buffer,sizeof(buffer)))>0)
{
int updateParam = UPDATE1;
int i,j;
i=0;
j=0;
for (;i<count;i++) {
if (buffer[i]!='\n'&&buffer[i]!=SPACE&&buffer[i]!='\0') {
switch (updateParam){
case UPDATE1:
directoryPath[j] = buffer[i];
break;
case UPDATE2:
inputFilePath[j] = buffer[i];
break;
case UPDATE3:
outputFilePath[j] = buffer[i];
break;
}
j++;
} else{
switch (updateParam){
case UPDATE1:
updateParam = UPDATE2;
j=0;
break;
case UPDATE2:
updateParam = UPDATE3;
j=0;
break;
}
}
}
}
if (count < 0) {
handleFailure();
}
}
but it is incredibly unintuitive and pretty ugly, so I thought there must be a more elegant way to do it. are there any suggestions?
Thanks!
Update: a config file content will look like that:
/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt
Your question isn't one about parsing the contents of the file, it is simply one about reading the lines of the file into adequate storage within a function in a manner that the object containing the stored lines can be return to the calling function. This is fairly standard, but you have a number of ways to approach it.
The biggest consideration is not knowing the length of the lines to be read. You say there are currently 3-lines to be read, but there isn't any need to know beforehand how many lines there are (by knowing -- you can avoid realloc, but that is about the only savings)
You want to create as robust and flexible method you can for reading the lines and storing them in a way that allocates just enough memory to hold what is read. A good approach is to declare a fixed-size temporary buffer to hold each line read from the file with fgets and then to call strlen on the buffer to determine the number of characters required (as well as trimming the trailing newline included by fgets) Since you are reading path information the predefined macro PATH_MAX can be used to adequately size your temporary buffer to insure it can hold the maximum size path usable by the system. You could also use POSIX geline instead of fgets, but we will stick to the C-standard library for now.
The basic type that will allow you to allocate storage for multiple lines in your function and return a single pointer you can use in the calling function is char ** (a pointer to pointer to char -- or loosely an dynamic array of pointers). The scheme is simple, you allocate for some initial number of pointers (3 in your case) and then loop over the file, reading a line at a time, getting the length of the line, and then allocating length + 1 characters of storage to hold the line. For example, if you allocate 3 pointers with:
#define NPATHS 3
...
char **readcmdfile (FILE *fp, size_t *n)
{
...
char buf[PATH_MAX] = ""; /* temp buffer to hold line */
char **paths = NULL; /* pointer to pointer to char to return */
size_t idx = 0; /* index counter (avoids dereferencing) */
...
paths = calloc (NPATHS, sizeof *paths); /* allocate NPATHS pointers */
if (!paths) { /* validate allocation/handle error */
perror ("calloc-paths");
return NULL;
}
...
while (idx < NPATHS && fgets (buf, sizeof buf, fp)) {
size_t len = strlen (buf); /* get length of string in buf */
...
paths[idx] = malloc (len + 1); /* allocate storage for line */
if (!paths[idx]) { /* validate allocation */
perror ("malloc-paths[idx]"); /* handle error */
return NULL;
}
strcpy (paths[idx++], buf); /* copy buffer to paths[idx] */
...
return paths; /* return paths */
}
(note: you can eliminate the limit of idx < NPATHS, if you include the check before allocating for each string and realloc more pointers, as required)
The remainder is just the handling of opening the file and passing the open file-stream to your function. A basic approach is to either provide the filename on the command line and then opening the filename provided with fopen (or read from stdin by default if no filename is given). As with every step in your program, you need to validate the return and handle any error to avoid processing garbage (and invoking Undefined Behavior)
A simple example would be:
int main (int argc, char **argv) {
char **paths; /* pointer to pointer to char for paths */
size_t i, n = 0; /* counter and n - number of paths read */
/* open file given by 1st argument (or read stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-failed");
return 1;
}
paths = readcmdfile (fp, &n); /* call function to read file */
/* passing open file pointer */
if (!paths) { /* validate return from function */
fprintf (stderr, "error: readcmdfile failed.\n");
return 1;
}
for (i = 0; i < n; i++) { /* output lines read from file */
printf ("path[%lu]: %s\n", i + 1, paths[i]);
free (paths[i]); /* free memory holding line */
}
free (paths); /* free pointers */
return 0;
}
Putting all the pieces together, adding the code the trim the '\n' read and included in buf by fgets, and adding an additional test to make sure the line you read actually fit in buf, you could do something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /* for PATH_MAX */
#define NPATHS 3
/* read lines from file, return pointer to pointer to char on success
* otherwise return NULL. 'n' will contain number of paths read from file.
*/
char **readcmdfile (FILE *fp, size_t *n)
{
char buf[PATH_MAX] = ""; /* temp buffer to hold line */
char **paths = NULL; /* pointer to pointer to char to return */
size_t idx = 0; /* index counter (avoids dereferencing) */
*n = 0; /* zero the pointer passed as 'n' */
paths = calloc (NPATHS, sizeof *paths); /* allocate NPATHS pointers */
if (!paths) { /* validate allocation/handle error */
perror ("calloc-paths");
return NULL;
}
/* read while index < NPATHS & good read into buf
* (note: instead of limiting to NPATHS - you can simply realloc paths
* when idx == NPATHS -- but that is for later)
*/
while (idx < NPATHS && fgets (buf, sizeof buf, fp)) {
size_t len = strlen (buf); /* get length of string in buf */
if (len && buf[len - 1] == '\n') /* validate last char is '\n' */
buf[--len] = 0; /* overwrite '\n' with '\0' */
else if (len == PATH_MAX - 1) { /* check buffer full - line to long */
fprintf (stderr, "error: path '%lu' exceeds PATH_MAX.\n", idx);
return NULL;
}
paths[idx] = malloc (len + 1); /* allocate storage for line */
if (!paths[idx]) { /* validate allocation */
perror ("malloc-paths[idx]"); /* handle error */
return NULL;
}
strcpy (paths[idx++], buf); /* copy buffer to paths[idx] */
}
*n = idx; /* update 'n' to contain index - no. of lines read */
return paths; /* return paths */
}
int main (int argc, char **argv) {
char **paths; /* pointer to pointer to char for paths */
size_t i, n = 0; /* counter and n - number of paths read */
/* open file given by 1st argument (or read stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("fopen-failed");
return 1;
}
paths = readcmdfile (fp, &n); /* call function to read file */
/* passing open file pointer */
if (!paths) { /* validate return from function */
fprintf (stderr, "error: readcmdfile failed.\n");
return 1;
}
for (i = 0; i < n; i++) { /* output lines read from file */
printf ("path[%lu]: %s\n", i + 1, paths[i]);
free (paths[i]); /* free memory holding line */
}
free (paths); /* free pointers */
return 0;
}
(note: if you allocate memory -- it is up to you to preserve a pointer to the beginning of each block -- so it can be freed when it is no longer needed)
Example Input File
$ cat paths.txt
/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt
Example Use/Output
$ ./bin/readpaths <paths.txt
path[1]: /home/bla/dirname
path[2]: /home/bla/bla/file1.txt
path[3]: /home/bla/bla/file2.txt
As you can see the function has simply read each line of the input file, allocated 3 pointers, allocated for each line and assigned the address for each block to the corresponding pointer and then returns a pointer to the collection to main() where it is assigned to paths there. Look things over and let me know if you have further questions.
I recommend looking into regular expressions. That way you read everything, then match with regular expressions and handle your matches.
Regular expressions exist for this purpose: to make parsing elegant.
If I were you, I will create a method for if/else blocks. I feel like they're redundant.
switch(updateParam) {
case UPDATE1:
method(); /*do if/else here*/
break;
...............
...............
}
However, you can still put them there if you do not need the method for other times and you concern about performance issues as function call costs more than just collective instructions.
In your program, you are passing 3 array of char to store the 3 lines read from the file. But this is very inefficient as the input file may contain more lines and in future, you may have the requirement to read more than 3 lines from the file. Instead, you can pass the array of char pointers and allocate memory to them and copy the content of lines read from the file. As pointed by Jonathan (in comment), if you use standard I/O then you can use function like fgets() to read lines
from input file.
Read a line from the file and allocate memory to the pointer and copy the line, read from the file to it. If the line is too long, you can read remaining part in consecutive calls to fgets() and use realloc to expand the existing memory, the pointer is pointing to, large enough to accommodate the remaining part of the line read.
Putting these all together, you can do:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SZ 100
#define MAX_LINES 3 /* Maximum number of lines to be read from file */
int readCMDFile(const char* cmdFile, char *paths[MAX_LINES]) {
int count, next_line, line_cnt, new_line_found;
char tmpbuf[BUF_SZ];
FILE *fp;
fp = fopen(cmdFile, "r");
if (fp == NULL) {
perror ("Failed to open file");
return -1;
}
next_line = 1; /* Keep track of next line */
count = 1; /* Used to calculate the size of memory, if need to reallocte
* in case when a line in the file is too long to read in one go */
line_cnt = 0; /* Keep track of index of array of char pointer */
new_line_found = 0;
while ((line_cnt < MAX_LINES) && (fgets (tmpbuf, BUF_SZ, fp) != NULL)) {
if (tmpbuf[strlen(tmpbuf) - 1] == '\n') {
tmpbuf[strlen(tmpbuf) - 1] = '\0';
new_line_found = 1;
} else {
new_line_found = 0;
}
if (next_line) {
paths[line_cnt] = calloc (sizeof (tmpbuf), sizeof (char));
if (paths[line_cnt] == NULL) {
perror ("Failed to allocate memory");
return -1;
}
next_line = 0;
count = 1;
} else {
char *ptr = realloc (paths[line_cnt], sizeof (tmpbuf) * (++count));
if (ptr == NULL) {
free (paths[line_cnt]);
perror ("Failed to reallocate memory");
return -1;
} else {
paths[line_cnt] = ptr;
}
}
/* Using strcat to copy the buffer to allocated memory because
* calloc initialize the block of memory with zero, so it will
* be same as strcpy when first time copying the content of buffer
* to the allocated memory and fgets add terminating null-character
* to the buffer so, it will concatenate the content of buffer to
* allocated memory in case when the pointer is reallocated */
strcat (paths[line_cnt], tmpbuf);
if (new_line_found) {
line_cnt++;
next_line = 1;
}
}
fclose(fp);
return line_cnt;
}
int main(void) {
int lines_read, index;
const char *file_name = "cmdfile.txt";
char *paths[MAX_LINES] = {NULL};
lines_read = readCMDFile(file_name, paths);
if (lines_read < 0) {
printf ("Failed to read file %s\n", file_name);
}
/* Check the output */
for (index = 0; index < lines_read; index++) {
printf ("Line %d: %s\n", index, paths[index]);
}
/* Free the allocated memory */
for (index = 0; index < lines_read; index++) {
free (paths[index]);
paths[index] = NULL;
}
return 0;
}
Output:
$ cat cmdfile.txt
/home/bla/dirname
/home/bla/bla/file1.txt
/home/bla/bla/file2.txt
$ ./a.out
Line 0: /home/bla/dirname
Line 1: /home/bla/bla/file1.txt
Line 2: /home/bla/bla/file2.txt
Note that the above program is not taking care of empty lines in the file as it has not been mentioned in the question. But if you want, you can add that check just after removing the trailing newline character from the line read from the file.