1

The following code gets a original txt file, a string to search in the file, and a new string to replace the original one. Two strings' length would be same. This code creates a new file ("new.txt"), writes the replaced text there, and then remove the original file & rename the new one as the original.

The question is, how can I make this code to function the same but do not create a new file? In other words, I want to just modify the original file itself. I tried fprintf to the original file (f), but its output was weird.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 1024

int main(int argc, char *argv[])
{
    FILE *f = fopen(argv[1], "r+");
    FILE *f2 = fopen("new.txt", "a+");

    if(strlen(argv[2])!=strlen(argv[3]))
        printf("[%s] and [%s] have different lengths\n", argv[2], argv[3]);

    char write[MAX];
    int where;
    char* string = NULL;

    int len = strlen(argv[2]);
    int i=0;

    while(fgets(write, MAX, f)!=NULL)
    {
        if(NULL!=(string = strstr(write, argv[2])))
        {
            where = (int)(string - write);
            strncpy(write+where, argv[3], len);
        }
        fprintf(f2, "%s", write);
    }
    remove(argv[1]);
    rename("new.txt", argv[1]);
    return 0;
}
5
  • 4
    If I wanted to work in-place, I'd probably just mmap the file. Commented May 24, 2018 at 4:53
  • Use fputs to write to the same file. Commented May 24, 2018 at 4:59
  • Do a check on fseek Commented May 24, 2018 at 5:01
  • fseek to the point in the file where the first change is to take place and then fwrite up to the point where no further changes are required. If you are inserting a line, that basically means overwriting everything from that line to the end of file. But that actually creates a new file on the file system, it just happens to have the same name. The seeking and writing of data will keep the file locked for a longer time than if you simply write to a new file, delete the original and rename the new file. Commented May 24, 2018 at 5:01
  • It would be better to check the string lengths before spending time opening the files — file opening is slow, especially if you're creating one. Of course, you'll end up only using one file. You'll need to keep track of where each line starts so that you can do the replacement, seek to the start of where you need to replace, write, and seek again (seek zero bytes from current position is good enough) before continuing to read. You must do a positioning operation between each sequence of reads and the next write, and also between a sequence of writes (one in this case) and the next read. Commented May 24, 2018 at 5:18

1 Answer 1

3

The short answer is you really shouldn't, but it is doable if the search and replace strings are exactly the same length -- but you cannot make any mistakes in the number of characters you write -- or you will corrupt the file.

In your code, if the strlen isn't the same, you need to return or otherwise handle the error, not just output that fact.

You already have f open as "r+" with the file-position-indicator at the beginning, strstr will tell you if you have found the word to replace, then all you need to do is set the file-positon-indicator to the location of the pointer returned by strstr and write the replacement characters over the search term, and repeat for the remainder of the line.

Keeping the offset for the file position indicater straight will need careful attention. After your read, the indicator will be one past the last character read by fgets, so you will need to backup by where - write - strlen(write) in your code. (note: the offset is intended to be negative)

You can use fseek to rewind and reset the file-position-indicator, but you are probably better served using fgetpos to save the current indicator position and fsetpos to restore it, while calling fseek only once to position to indicator for the replacement.

Putting it altogether in a short example, and using buf, find and replace, instead of write and where, you could do something like the following:

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

#define MAXC 1024

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

    char buf[MAXC], *find, *replace;    /* read buf, find, replace pointers */
    size_t findlen;     /* length of find string */
    FILE *f = NULL;     /* file pointer */

    if (argc < 4 ) {    /* validate sufficient arguments given */
        fprintf (stderr, "error: insufficient input, "
                "usage: %s file find repl\n", argv[0]);
        return 1;
    }
    find = argv[2];     /* set find, replace & length */
    replace = argv[3];
    findlen = strlen (find);

    if (findlen != strlen (argv[3])) {  /* validate length the same */
        fprintf (stderr, "error find/replace lengths differ.\n");
        return 1;
    }

    if (!(f = fopen (argv[1], "r+"))) {  /* validate file open for reading+ */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (fgets (buf, MAXC, f)) {  /* read each line into buf */
        char *findp = buf;          /* find pointer to search buf */
        /* 
         * buf length and search term split at end validation omitted.
         */
        while ((findp = strstr (findp, find))) {    /* while find found */
            fpos_t pos;         /* object to hold current file position */
            /* compute characters to backup (negative) */
            long backup = (long)(findp - buf) - strlen(buf);

            fgetpos (f, &pos);              /* save the current position */
            fseek (f, backup, SEEK_CUR);    /* backup */

            for (char *p = replace; *p; p++)
                fputc (*p, f);  /* overwrite char-by-char */

            fsetpos (f, &pos);  /* reset file position */

            findp += findlen;   /* advance beyond current find */
        }
    }

    if (fclose (f) == EOF)    /* validate close after write */
        perror ("fclose(f)");

    return 0;
}

Example Input File

$ cat dat/dogfleas.txt
my dog has fleas
other dogs run away
my dog is an ichy dog

Example Use and Resulting File

$ ./bin/file_replace_in_place dat/dogfleas.txt dog cat

$ cat dat/dogfleas.txt
my cat has fleas
other cats run away
my cat is an ichy cat

(no pun in shell command intended)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.