Question

I am trying to read a specific line from a file using the code below.

char *getlinenum(char *filename, int lnum)
{
    FILE *f;
    int i;
    char *linebuf = NULL, *tmp = NULL;
    if ((f = fopen(filename, "r")) != NULL)
    {
        linebuf = (char *)malloc(2048);
        memset(linebuf, 0, 2048);
        for (i = 0; i < lnum; i++)
        {
            if (fscanf(f, "%[^\n]\n", linebuf) == EOF)
            {
                free(linebuf);
                fclose(f);
                printf("Returning NULL\n");
                return NULL;
            }
        }
        //tmp = strdup(linebuf);
        //free(linebuf);
        fclose(f);
        return linebuf;
    }
    return NULL;
}

No matter what, this is always returning just an empty (zero) string. Any issues you see? Here is the test file:

/home/mainframe/b
/home/mainframe/dead.letter
/home/mainframe/.bash_history
/home/mainframe/a
/home/mainframe/f
/home/mainframe/e
/home/mainframe/c
/home/mainframe/g
/home/mainframe/.ssh/authorized_keys
/home/mainframe/.ssh
/home/mainframe/d

I don't really understand where it could end up with a zero string (not a nullpointer).

Was it helpful?

Solution

This code is working for me (made no changes except removing uncesessary tmp variable)

One issue is if 0 is passed, the for loop never enters. Just change it to <= and/or add another if-statement at the beginning:

if( lnum <= 0 ) 
  return NULL;

to catch this issue.

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

char *getlinenum(char *filename, int lnum)
{
    FILE *f;
    int i;
    char *linebuf = NULL;

    if( lnum <= 0 ) 
      return NULL;

    if ((f = fopen(filename, "r")) != NULL)
    {
        linebuf = (char *)malloc(2048);
        memset(linebuf, 0, 2048);
        for (i = 0; i <= lnum; i++)
        {
            if (fscanf(f, "%[^\n]\n", linebuf) == EOF)
            {
                free(linebuf);
                fclose(f);
                printf("Returning NULL\n");
                return NULL;
            }
        }
        free(linebuf);
        fclose(f);
        return linebuf;
    }
    return NULL;
}

int main()
{
   printf("%s\n", getlinenum("input.txt", 2));
   return 0;
}

Output:

/home/mainframe/dead.letter

OTHER TIPS

A fairly obvious problem not mentioned yet is that this code overflows the buffer if there is a line longer than 2048.

Another problem is that your fscanf string will skip blank lines (except for the first line of the file). I'm not sure if this was intentional. The \n matcher that you have on the end of the string means to match all whitespace up till the next non-whitespace even if that whitespace includes multiple newlines.

To fix that problem, you could remove that \n and just do a fgetc() after each fscanf to consume one newline.

To fix the buffer overflow I would recommend skipping up to the line you want without storing anything, and then using a fgets to get the line you are interested in. For example (here I also have factored out the cleanup code):

if (lnum < 1 || (f = fopen(filename, "r")) == NULL)
    return NULL;

char *buffer = NULL;

for ( ; lnum > 1; --lnum )
{
     if ( fscanf(f, "%*[^\n]") == EOF || fgetc(f) == EOF ) 
          break;
}

if ( lnum == 1 )
{
// or use the POSIX getline() function or similar, to avoid any size limitation and
// avoid the mucking around with fgets and \n
   buffer = calloc(1, 2048);

    if ( ! fgets(buffer, 2048, f) )
    { 
        free(buffer); 
        buffer = NULL;
    }
    else if ( buffer[0] && buffer[strlen(buffer)-1] == '\n' )
       buffer[strlen(buffer)-1] = 0;
}

fclose(f);
return buffer;

Also, using unsigned long long for line_num would let you read more!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top