Ode To Sleep
Ode To Sleep

Reputation: 21

Find and replace specific string in a text file, without creating new file

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;
}

Upvotes: 1

Views: 1196

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84559

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)

Upvotes: 3

Related Questions