In C++ How to Go to a Specific Line in a Text File

How to fgets() a specific line from a file in C?

Unless you know something more about the file, you can't access specific lines at random. New lines are delimited by the presence of line end characters and they can, in general, occur anywhere. Text files do not come with a map or index that would allow you to skip to the nth line.

If you knew that, say, every line in the file was the same length, then you could use random access to jump to a particular line. Without extra knowledge of this sort you simply have no choice but to iterate through the entire file until you reach your desired line.

C Programming - Read specific line from text file

With this code you can read a file line by line and hence read a specific line from the text file:

lineNumber = x;

static const char filename[] = "file.txt";
FILE *file = fopen(filename, "r");
int count = 0;
if ( file != NULL )
{
char line[256]; /* or other suitable maximum line size */
while (fgets(line, sizeof line, file) != NULL) /* read a line */
{
if (count == lineNumber)
{
//use line or in a function return it
//in case of a return first close the file with "fclose(file);"
}
else
{
count++;
}
}
fclose(file);
}
else
{
//file doesn't exist
}

C: Write to a specific line in the text file without searching

I will assume that you are going to be doing this many times for a single file, as such you would be better indexing the position of each newline char, for example you could use a function like this:

long *LinePosFind(int FileDes)
{
long * LinePosArr = malloc(500 * sizeof(long));
char TmpChar;
long LinesRead = 0;
long CharsRead = 0;
while(1 == read(FileDes, &TmpChar, 1))
{
if (!(LinesRead % 500)
{
LinePosArr = realloc(LinePosArr, (LinesRead + 500) * sizeof(long));
}
if (TmpChar == '\n')
{
LinePosArr[LinesRead++] = CharsRead;
}
CharsRead++;
}
return LinePosArr;
}

Then you can save the index of all the newlines for repeated use.

After this you can use it like so:

long *LineIndex = LinePosFind(FileDes);
long FourthLine = LineIndex[3];

Note I have not checked this code, just written from my head so it may need fixes, also, you should add some error checking for the malloc and read and realloc if you are using the code in production.

How to edit a specific line of a txt file in C

What you are missing is somewhat conceptual, and somewhat related to fopen. When you think about opening a file with fopen, you need to pay particular attention to the effect of the file modes. If you look carefully at the man page regarding either "w" or "w+". In both cases the existing file is truncated. To 0-length in the case of "w".

To avoid this issue, one approach is to read the entire file into a buffer and then make changes to the buffer, writing the modified buffer back to the original filename. This avoids the possibility to attempting to insert/delete bytes without rewriting the remainder of the file.

To handle reading the file into a buffer, the link posted overwriting a specific line on a text file?, provides a roadmap to changing a single line in a file. Your case is different. You want to find/replace All occurrences of a particular pattern. (that is where the truncation issue posses challenges) However much of the solution there can be applied to reading the file itself into a buffer. Specifically the use of fseek and ftell.

Using fseek and ftell provides a simply way to determine the size (or length) of the file that can then be used to allocate space to hold the entire file in memory. Below is one approach to a simple function that takes the address of a character pointer and a file pointer, then using fseek and ftell allocates the required memory to hold the file and then reads the file into the buffer (filebuf) in a single operation with fread. The buffer is filled, in place, and also returned. A pointer to the file length fplen is passed to the function so the length is made available back in the calling function (main() in this case). Returning a pointer to the buffer on success (NULL otherwise) will allow assignment of the return, if desired, and a way to determine success/failure of the read:

char *read_file_into_buf (char **filebuf, long *fplen, FILE *fp)
{
fseek (fp, 0, SEEK_END);
if ((*fplen = ftell (fp)) == -1) { /* get file length */
fprintf (stderr, "error: unable to determine file length.\n");
return NULL;
}
fseek (fp, 0, SEEK_SET); /* allocate memory for file */
if (!(*filebuf = calloc (*fplen, sizeof *filebuf))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return NULL;
}

/* read entire file into filebuf */
if (!fread (*filebuf, sizeof *filebuf, *fplen, fp)) {
fprintf (stderr, "error: file read failed.\n");
return NULL;
}

return *filebuf;
}

Once you have the file in memory, the second piece of the puzzle is simply to scan through the buffer and make the replacements you need. Here there are a number of different tweaks you can apply to optimize the search/replace, but the following is just a straight forward basic search/replace where the only optimization attempt is a comparison of the starting character before using the normal string.h string comparison functions to check for your specified search string. The function returns the number of replacements made so you can determine whether a write out to the original filename is required:

unsigned find_replace_text (char *find, char *rep, char *buf, long sz)
{
long i;
unsigned rpc = 0;
size_t j, flen, rlen;

flen = strlen (find);
rlen = strlen (rep);

for (i = 0; i < sz; i++) {
/* if char doesn't match first in find, continue */
if (buf[i] != *find) continue;

/* if find found, replace with rep */
if (strncmp (&buf[i], find, flen) == 0) {
for (j = 0; buf[i + j] && j < rlen; j++)
buf[i + j] = rep[j];
if (buf[i + j])
rpc++;
}
}

return rpc;
}

Putting all the pieces together in a short example program using your sample data could be written as follows. The program expects the filename as the first argument (or it will read from stdin and write to stdout by default if no filename is given). There are always additional validation checks you can include as well:

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

char *read_file_into_buf (char **filebuf, long *fplen, FILE *fp);
unsigned find_replace_text (char *find, char *rep, char *buf, long sz);

int main (int argc, char **argv) {

char *srchstr = "400,300";
char *repstr = "400,300: (000,000,000) #000000";
char *filebuf = NULL;
long int fplen = 0;
FILE *fp = NULL;

/* open file for reading (default stdin) */
fp = argc > 1 ? fopen (argv[1], "r") : stdin;

if (!fp) { /* validate file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
return 1;
}

if (!read_file_into_buf (&filebuf, &fplen, fp)) return 1;
if (fplen < 1 || fplen >= INT_MAX) { /* validate file length */
fprintf (stderr, "error: length of file invalid for fwrite use.\n");
return 1;
}
if (fp != stdin) fclose (fp);

/* find/replace text in filebuf */
if (!find_replace_text (srchstr, repstr, filebuf, fplen)) {
printf ("no replacements made.\n");
return 0;
}

/* open file for writing (default stdout) */
fp = argc > 1 ? fopen (argv[1], "w") : stdout;

if (!fp) { /* validate file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
return 1;
}

/* write modified filebuf back to filename */
if (fwrite (filebuf, sizeof *filebuf, (size_t)fplen, fp) != (size_t)fplen) {
fprintf (stderr, "error: file write failed.\n");
return 1;
}
if (fp != stdout)
if (fclose (fp) == EOF) {
fprintf (stderr, "error: fclose() returned EOF\n");
return 1;
}

free (filebuf);

return 0;
}

Just include the functions at the bottom of the file. You can then:

Compile

gcc -Wall -Wextra -O3 -o bin/fread_file fread_file.c

(or use the equivalent compile string with your compiler)

Input File

$ cat dat/rbgtst.txt
400,280: (234,163,097) #EAA361
400,300: (255,255,255) #FFFFFF
400,320: (064,101,160) #4065A0
400,340: (220,194,110) #DCC26E

Use/File After Replacement

$ ./bin/fread_file dat/rbgtst.txt
$ cat dat/rbgtst.txt
400,280: (234,163,097) #EAA361
400,300: (000,000,000) #000000
400,320: (064,101,160) #4065A0
400,340: (220,194,110) #DCC26E

or reading from stdin writing to stdout:

$ ./bin/fread_file <dat/rbgtst.txt
400,280: (234,163,097) #EAA361
400,300: (000,000,000) #000000
400,320: (064,101,160) #4065A0
400,340: (220,194,110) #DCC26E

Memory/Error Check

In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an unintitialized value and finally to confirm that you have freed all the memory you have allocated.

For Linux valgrind is the normal choice. There are many subtle ways to misuse a new block of memory. Using a memory error checker allows you to identify any problems and validate proper use of of the memory you allocate rather than finding out a problem exist through a segfault. There are similar memory checkers for every platform. They are all simple to use, just run your program through it. E.g.:

$ valgrind ./bin/fread_file dat/rbgtst.txt
==13768== Memcheck, a memory error detector
==13768== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==13768== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==13768== Command: ./bin/fread_file dat/rbgtst.txt
==13768==
==13768==
==13768== HEAP SUMMARY:
==13768== in use at exit: 0 bytes in 0 blocks
==13768== total heap usage: 3 allocs, 3 frees, 2,128 bytes allocated
==13768==
==13768== All heap blocks were freed -- no leaks are possible
==13768==
==13768== For counts of detected and suppressed errors, rerun with: -v
==13768== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

You want to confirm All heap blocks were freed -- no leaks are possible and ERROR SUMMARY: 0 errors from 0 contexts (ignore the suppressed note which simply relates to missing debug symbol files not installed on my system)

Look over the code and understand what it is doing. This isn't presented as the only way of doing what you are attempting to do, but it is presented as an example of how to approach the problem while avoiding a number of pitfalls inherent in trying to change a line-at-a-time in an existing file utilizing offsets and a number of reads/writes to the file. Let me know if you have questions.



Related Topics



Leave a reply



Submit