2

I am currently trying to edit specific lines of .txt file in C. The file that im using looks like this :

Pixel location and RGB Color

Now lets say I want to change whats written on the specific line that its highlighted on the image:

400,300: (255,255,255) #FFFFFF

into this:

400,300: (000,000,000) #000000

Basically, im trying to create a black dot in specific pixels, in this case on 400,300. This is what i have of code:

#include <stdio.h>
int main(void)
{
    const char *filename = "sample.txt";
    int x = 400;
    int y = 300;

    FILE *fp;
    fp = fopen(filename, "w+");

    // Algorithm that reads all the file

    // If("Operation that reads" == x+","+y)
    // {
    //     Replace the line information after where it starts with "400,300"
    //     Like this : 400,300: (000,000,000) #000000
    // }


    // Algorithm that saves the file with the changes.


    fclose(fp)
    printf("Ok - File %s saved\n", filename);
    return 0;

Creating, opening and editing .txt files is kind of new for me so I dont know what to do, the more i read about it, the more confused I get. How do I approach this problem and what code would fit here?

Update 1:

FILE *fp;
fp = fopen(filename, "w+");

if ( fp == NULL )
{
    printf("Error while opening file"); 
}   

Ok so after reading what you have placed below i came up with an idea but still needs work. I would print everything from the file to a char array. After that i would search in each slot for the specific line of code that I was looking for and keep the number slot. After that, i would go to array, run it, and when it comes to that specific slot, i would replace the needed data. Now all i needed to do is to swap the information thats in the file for the one thats in the array, save the file and problem solved. But im getting erros in the code and im missing the bits of code that would clear the txt file and save the new data.

Update 2:

#include <stdio.h>

int main(void)
{

int x,y;
int k = 0;
int noline; // Used to locate which line is the string im looking for
char search; // Used to compare with each string
char blackcode = (char)000; // In RGB, Black uses (000,000,000)
char blackhexcode = (char)000000; // The hexcode for black is #000000
const char *filename = "sample.txt";
char* strings[480000]; // Since its a 800x600 resolution picture, it needs that many lines.
char line[30]; // Space created to store whats inside each line of the file before transfering
char temp;

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

if ( fp == NULL )
{
    printf("Error while opening file"); 
}   
else
{
    while(fgets(line, sizeof line, fp))
    {       
        strings[k]=strdup(line); // ERROR HERE! What Am I missing?
        k++;
    }

    for(k = 0; k< sizeof strings; k++)
    {
        temp = scanf("%[^:]s", strings[k]);
        search = ("%s,%s",x,y);

        if(temp = search)
        {
            noline = k;
        }
        else
        {
            printf("Error : Wrong Coordinates");
        }
    }

    for(k = 0; k < sizeof strings; k++)
    {
        if(k == noline)
        {
            strings[k] = ("%d,%d: (%s,%s,%s) #%s", x, y, blackcode, blackcode, blackcode, blackhexcode); // ERROR HERE! What did i did wrong?
        }
    }

    // Code that cleans the txt file and saves the array back to txt file
}
fclose(fp);

}

Manuel Cruz
  • 53
  • 1
  • 1
  • 8
  • 3
    When I google the title question, there are numerous avenues to follow. – Weather Vane Dec 30 '15 at 20:10
  • But the code posted has an error already: asssuming that `fclose` successfully closed the file. That's very important when writing to a file: `if (fclose(fp)) /* report error */` – Weather Vane Dec 30 '15 at 20:14
  • 1
    This is so much more easily done with the shell, e.g., with `sed`. Do you have to do it with C? – royhowie Dec 30 '15 at 20:30
  • I've read tons of posts about it but none are actually helping me at all. Will try to use "r" first then "w" Will look into that error It has to be with C – Manuel Cruz Dec 30 '15 at 20:44
  • As @royhowie mentions, the shell solutions is fairly simple, just `sed -i 's/400,300: (255,255,255) #FFFFFF/400,300: (000,000,000) #000000/' filename`. – David C. Rankin Dec 30 '15 at 22:04
  • "Tons of posts" did not help you - including the one @ryyker points to below: http://stackoverflow.com/q/1832757/2564301? Then you may need to [edit] your post and add what exactly fails, at what point. – Jongware Dec 30 '15 at 23:01
  • That post does seem to lay out exactly what to use and what is required to solve the problem. @ManuelCruz, attempt a solution with the suggestions provided, if you run into problems, post what you have attempted and we are happy to help further. – David C. Rankin Dec 31 '15 at 00:00

3 Answers3

6

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.

Community
  • 1
  • 1
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

You cannot write specific line of txt file in general.

Actually, txt file is just a sequence of bytes. Every line separated by each other just by special symbol '\n' (or symbols '\r', '\n': there are two approaches).

So, if you rewrite some line, you have to move data (lines) remained in the file just after your new line.

But if your new line has the same length as before, you can write it over old line without any worries.

Anton Gorev
  • 434
  • 2
  • 13
  • _You cannot write specific line of txt file in general_... Are you sure? ***[hint](http://stackoverflow.com/a/1832769/645128)*** – ryyker Dec 30 '15 at 21:43
  • Note that OP uses the same 3-digit format for the new line. So *in this particular case* it can be done. You say this ought to work but nothing more. Care to back it up with code? – Jongware Dec 30 '15 at 22:58
0

The best approach I can think of for something like this is to open the file in read only mode and then copy everything to a new folder by opening a new file in 'w+' mode. Then you go line by line in the read file until you find a line that you wish to change, then you rewrite the line yourself in the new copy file. Then skip that line in the read file and continue on. After the copy file is what you want, you can replace the name of it to the original file name you want it to have. Then it will act as if you edited the file like you wanted to.