1

I' am writing a C program which allows the user to dynamically specify the File name from which the data is to be read. Next the user enters a lower bound and an upper bound. The data in the lines from between the bounds is to be printed.

For this the main function makes a call: readValues(cTargetName, iLower, iHiger);

The function readValues is supposed to work as follows:

  1. Check if file exist, if yes. Open it with fopen
  2. Read with feof and fgets line by line the whole file, and store each line in char string
  3. With a for loop, print the correct range of lines from the string

I'm not sure why but the while loop doesn't seem to exit although I use the feof statement, which should terminate after the end of the File is reached.

The code looks as follows:

#include <stdio.h>
#include <stdlib.h>

void readValues(char cFileName[75], int n, int m)
{
    //Variable declaration;
    char strArray[50][50];
    char *parser;
    int i = 0;

    FILE *Data;

    if(Data = fopen(cFileName, "rt") == NULL){
        printf("File could not be opened");
        return 1; //Can you return 1 in a void function?
    }

    //Read the file line by line
    while(feof(Data)==0){
        fgets(strArray[i], 200, Data);
        i++;
    }

    //Reading the specified lines
    for(n; n<=m; n++){
        printf("%s", strArray[n]);
    }
}


int main()
{
    char cTargetName[75] = {"C:/Users/User1/Desktop/C_Projects_1/TestData.txt"};
    int iLower = 2;
    int iHiger = 4;

    readValues(cTargetName, iLower, iHiger);

    return 0;
}

All help is appreciated. Thanks in advance!

Mark
  • 131
  • 9
  • 2
    `feof` does not return true if the next read will reach the end of the file. It returns true if a previous read reached the end of the file. Never use `feof` to control a loop. Instead, call `fgets` and test its return value. – Eric Postpischil Jul 24 '22 at 10:19
  • 1
    The code shown will not compile because of the `return 1;` inside a function with return type `void`. When asking a question about a program that is not working, always include a [mre] that contains an exact copy of code that reproduces the problem. That code should include everything another person needs to reproduce the problem with no additions or changes, such as lines like `#include `. – Eric Postpischil Jul 24 '22 at 10:21
  • 1
    Additionally, the code contains other errors that a compiler will warn about. `Data = fopen(cFileName, "rt") == NULL` should be `(Data = fopen(cFileName, "rt")) == NULL)`. Enable warnings in your compiler and elevate warnings to errors. With Clang, start with `-Wmost -Werror`. With GCC, start with `-Wall -Werror`. With MSVC, start with `/W3 /WX`. Do not attempt to run the program until all warnings are fixed. – Eric Postpischil Jul 24 '22 at 10:23
  • @EricPostpischil Tanks for the comments. The code indeed works after I added the () around `(Data = fopen(cFileName, "rt"))`. One more question is it possible to exit the function analogue to `return 1;` within a void function? – Mark Jul 24 '22 at 10:32
  • 1
    just use `return;` – stark Jul 24 '22 at 11:05

3 Answers3

2

several issues here, first, you limited the length of lines to 200, not exactly what you might expect to get. the fgets function returns lines up to specified length unless hit by newline character - this should be taken into account. additionally, fgets returns NULL if you hit EOF - no real need to use feof.

second, you could save yourself a lot of pain and simply count the number of times you get a string, and for the times you are within the range just print it immediately. will save you a nice amount of overhead

like this:

#include <stdio.h>
#include <stdlib.h>
#define MAXLINE 200//or anything else you want
void readValues(char cFileName[75], int n, int m)
{
//Variable declaration;
char line[MAXLINE];
int i = 0;

FILE *Data;

if((Data = fopen(cFileName, "rt")) == NULL){
    printf("File could not be opened");
    return 1; //Can you return 1 in a void function?
}

//Read the file line by line and print within range of lines
while((line=fgets(line, MAXLINE,Data))!=NULL){//terminates upon EOF
    if (++i>=n&&i<=m)
        printf(""%s\n",line);
}

}
Josh Kuber
  • 51
  • 5
  • 1
    This answer is simply wrong. `if(Data = fopen(cFileName, "rt") == NULL)` assigns the value of `fopen(cFileName, "rt") == NULL)` to `Data`. It should be: `if((Data = fopen(cFileName, "rt")) == NULL)`. – Zakk Jul 24 '22 at 11:53
  • 1
    The same thing applies for `while(line=fgets(line, MAXLINE,Data)!=NULL)`. It should be: `while((line=fgets(line, MAXLINE,Data)) != NULL)`. – Zakk Jul 24 '22 at 11:54
  • Also, you shouldn't encourage beginners to use macros where constants should be used. Remove that `#define MAXLINE 200` and replace it with something like `const size_t size = 200;` _inside_ `main()`. – Zakk Jul 24 '22 at 11:56
  • 1
    @Zakk agreed, I used the question's code, just changed the parts to emphasize the parts to optimize -anyhow, I changed the code. as for the #define, that's a pretty basic use, every beginner should know how to define symbolic constants – Josh Kuber Jul 24 '22 at 12:14
  • _"every beginner should know how to define symbolic constants"_... The use of macros is discouraged. https://stackoverflow.com/questions/1637332/static-const-vs-define – Zakk Jul 24 '22 at 12:26
1

Here is my solution to your question:

#include <stdio.h>
#include <stdlib.h>

#define MIN_LINE_LENGTH 64

typedef enum {
    false, true
} bool;

int main() {

    char filename[PATH_MAX] = {0};
    printf("Enter filename:\n");
    fgets(filename, PATH_MAX, stdin); // get filename from stdin

    char *ptr = filename;

    while (*ptr) { // remove trailing newline at the end of filename (fgets() includes newline)
        if (*ptr == '\n') {
            *ptr = 0;
        }
        ++ptr;
    }

    printf("Enter starting line and end line, separated by a space:\n");

    size_t startLine = 0;
    size_t endLine = 0;

    bool hasFirstNum = false;
    bool hasSecondNum = false;
    bool hasMiddleSpace = false;
    bool hasLastSpace = false;

    size_t numCount = 0;

    int ch;
    while ((ch = fgetc(stdin)) != EOF && ch != '\n') { // continually receive chars from stdin
        if (ch != 32 && !(ch >= 48 && ch <= 57)) { // if not a space or number, raise error
            fprintf(stderr, "Only numerical values (and spaces) can be entered.\n");
            return 1;
        }
        if (ch == 32) {
            if (hasFirstNum) {
                hasMiddleSpace = true;
            }
            if (hasSecondNum) {
                hasLastSpace = true;
            }
            continue;
        }
        else if (!hasFirstNum) {
            ++numCount;
            hasFirstNum = true;
        }
        else if (!hasSecondNum && hasMiddleSpace) {
            ++numCount;
            hasSecondNum = true;
        }
        else if (hasLastSpace) {
            ++numCount;
        }
        if (numCount == 1) {
            startLine *= 10;
            startLine += ch - 48; // '0' character in ASCII is 48
        }
        else if (numCount == 2){
            endLine *= 10;
            endLine += ch - 48;
        }
        else {
            break;
        }
    }

    FILE *fp = fopen(filename, "r");

    if (fp == NULL) {
        fprintf(stderr, "Error opening file.\n");
        return 1;
    }

    char **lines = malloc(sizeof(char *));
    char *line = malloc(MIN_LINE_LENGTH);
    *lines = line;

    int c;

    size_t char_count = 0;
    size_t line_count = 1;

    while ((c = fgetc(fp)) != EOF) { // continually get chars from file stream
        if (c == '\n') { // expand lines pointer if a newline is encountered
            *(line + char_count) = 0;
            ++line_count;
            lines = realloc(lines, line_count*sizeof(char *));
            line = (*(lines + line_count - 1) = malloc(MIN_LINE_LENGTH));
            char_count = 0;
            continue;
        }
        if ((char_count + 1) % MIN_LINE_LENGTH == 0 && char_count != 0) { // expand line pointer if needed
            line = realloc(line, char_count + MIN_LINE_LENGTH);
        }
        *(line + char_count) = c;
        ++char_count;
    }

    
    *(line + char_count) = 0; // to ensure the last line always ends with the null byte

    if (startLine >= line_count) { // raise error if starting line specified is greater than num. of lines in doc.
        fprintf(stderr, "Specified starting line is less than total lines in document.\n");
        return 1;
    }
    if (endLine > line_count) { // adjust ending line if it is greater than number of lines in doc.
        endLine = line_count;
    }
    if (startLine == 0) { // we will be using the starting index of 1 as the first line
        startLine = 1;
    }

    char **linesPtr = lines + startLine - 1;
    while (startLine++ <= endLine) { // print lines
        printf("%s\n", *linesPtr++);
    }

    for (size_t i = 0; i < line_count; ++i) { // free all memory
        free(*(lines + i));
    }
    free(lines);

    return 0;
}

It is a little more convoluted, but because it uses dynamic memory allocation, it can handle lines of any length within a text file.

If there is anything unclear, please let me know and I would be happy to explain.

Hope this helps!!

  • You could have used `filename[strcspn(filename, "\n")] = '\0'` to remove the trailing new line. – Zakk Jul 24 '22 at 13:21
  • @Zakk indeed, you are right, but I prefer to avoid function calls where I can (if the alternative is quick to write on my own) – Gregor Hartl Watters Jul 24 '22 at 13:23
  • 1
    @GregorWattersHärtl Many thanks for your answer. It's impressive! However I do have a question. I see that you do use `EOF` for controlling the loops. I was told not to use `feof`, if I am not mistaken they do at the end of the day the same thing. Could you please explain what is the difference and why using `EOF` is fine but `feof` isn't? – Mark Jul 25 '22 at 12:57
  • 1
    @Mark yes of course! `feof(FILE *stream)` returns true (non-zero) if the end of file indicator for the given file stream has been set, and false otherwise (if the end of the file has not been reached yet). However, it only returns true if the end of file is reached by another function that is reading from the file, which is why it is suggested not to use it as a loop condition (since, e.g., for one iteration you might try and do something with what has been read from the file, but this would be invalid since the end of file has been reached). `EOF`, however, is a value that is returned by... – Gregor Hartl Watters Jul 25 '22 at 14:54
  • 1
    @Mark ... certain functions that read from file streams to indicate that the end of file has been reached (and that there is not more data left to read). Thus, testing for this return value can be used as a loop condition, since the loop will terminate the moment `EOF` is returned (like in my answer), thus not giving the opportunity to continue a single iteration after the end of file has been reached. – Gregor Hartl Watters Jul 25 '22 at 14:58
  • 1
    @Mark To sum up, (and forgive me for droning on) it is best to test for return values directly from the functions doing the reading in the loop condition, to test that the end-of-file has not been reached yet, rather than using `feof()`. If you do still wish to use `feof()`, however, you could do something like [this](https://www.tutorialspoint.com/c_standard_library/c_function_feof.htm), where the end-of-file condition is tested WITHIN the loop and BEFORE anything is done with the `char` (techincally `int`) value returned by `fgetc()`. Hope this helps! Don't hesitate to ask if anything :) – Gregor Hartl Watters Jul 25 '22 at 15:04
0

Two functions used :

  • fseek()
  • fgets()

Code :

#include<stdio.h>

void main(){

     FILE *fp;

     fp=fopen("file.txt","w");

     fputs("hello world",fp);

     fclose(fp);

     char r[10];

     fp=fopen(""file.txt","r");

     fseek(fp,3,0);

     printf("%s",fgets(r,6,fp));

     fclose(fp);

}
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 19 '23 at 18:30