Reputation: 5129
Okay, so after reading both: How to read a specific line in a text file in C (integers) and What is the easiest way to count the newlines in an ASCII file? I figured that I could use the points mentioned in both to both efficiently and quickly read a single line from a file.
Here's the code I have:
char buf[BUFSIZ];
intmax_t lines = 2; // when set to zero, reads two extra lines.
FILE *fp = fopen(filename, "r");
while ((fscanf(fp, "%*[^\n]"), fscanf(fp, "%*c")) != EOF)
{
/* globals.lines_to_feed__queue is the line that we _do_ want to print,
that is we want to ignore all lines up to that point:
feeding them into "nothingness" */
if (lines == globals.lines_to_feed__queue)
{
fgets(buf, sizeof buf, fp);
}
++lines;
}
fprintf(stdout, "%s", buf);
fclose(fp);
Now the above code works wonderfully, and I'm extrememly pleased with myself for figuring out that you can fscanf
a file up to a certain point, and then use fgets
to read whatever data is at said point into a buffer, instead of having to fgets
every single line and then fprintf
the buf, when all I care about is the line that I'm printing: I don't want to be storing strings that I could care less about in a buffer that I'm only going to use once for a single line.
However, the only issue I've run into, as noted by the // when set to zero, reads two extra lines
comment: when lines
is initialized with a value of 0
, and the line I want is like 200
, the line I'll get will actually be line 202
. Could someone please explain what I'm doing wrong here/why this is happening and whether my quick fix lines = 2;
is fine or if it is insufficient (as in, is something really wrong going on here, and it just happens to work?)
Upvotes: 1
Views: 3077
Reputation: 754900
Just as another way of looking at the problem… Assuming that your global specifies 1
when the first line is to be printed, 2
for the second, etc, then:
char buf[BUFSIZ];
FILE *fp = fopen(filename, "r");
if (fp == 0)
return; // Error exit — report error.
for (int lineno = 1; lineno < globals.lines_to_feed_queue; lineno++)
{
fscanf(fp, "%*[^\n]");
if (fscanf(fp, "%*c") == EOF)
break;
}
if (fgets(buf, sizeof(buf), fp) != 0)
fprintf(stdout, "%s", buf);
else
…requested line not present in file…
fclose(fp);
You could replace the break with fclose(fp);
and return;
if that's appropriate (but do make sure you close the file before exiting; otherwise, you leak resources).
If your line numbers are counted from 0, then change the lower limit of the for
loop to 0
.
Upvotes: 1
Reputation: 9930
There are two reasons why you have to set the lines
to 2
, and both can be derived from the special case where you want the first line.
On one hand, in the while
loop the first thing you do is use fscanf
to consume a line, then you check if the lines
counter matches the line you want. The thing is that if the line you want is the one you just consumed you are out of luck. On the other hand you are basically moving through lines by finding the next \n
and incrementing lines
after you check if the current line is the one you're after.
These two factors combined cause the offset in the lines
count, so the following is a version of the same function taking them into account. Additionally it also contains a break;
statement once you get to the line you are looking for, so that the while
loop stops looking further into the file.
void read_and_print_line(char * filename, int line) {
char buf[BUFFERSIZE];
int lines = 0;
FILE *fp = fopen(filename, "r");
do
{
if (++lines == line) {
fgets(buf, sizeof buf, fp);
break;
}
}while((fscanf(fp, "%*[^\n]"), fscanf(fp, "%*c")) != EOF);
if(lines == line)
printf("%s", buf);
fclose(fp);
}
Upvotes: 3
Reputation: 7923
First, about what is wrong here: this code is unable to read the very first line in the file (what happens if globals.lines_to_feed__queue
is 0?). It would also miscount lines shall the file contain successive newlines.
Second, you must realize that there is no magic. Since you don't know at which offset the string in question lives, you have to patiently read file character by character, counting end-of-strings along the way. It doesn't matter if you delegate the reading/counting to fgets
/fscanf
, or fgetc
each character for manual inspection - either way an uninteresting piece of file will make its way from the disk into the OS buffers, and then into the userspace for interpretation.
Your gut feeling is absolutely correct: the code is broken.
Upvotes: 0