如何用 C 语言替换文本文件中的 string/phrase

How to replace a string/phrase in a text file in C

如何在简单的 C 程序中替换文本文件中给定字符串的每个实例?

有人会认为这是一个很容易用 Google 搜索到的问题,是吗?

在询问时,SanFoundry, TutorialsPoint, GeeksforGeeks, w3resource, Codeforwin, a blog, GeeksforGeeks (twice) and even Code Review SE 有严重损坏的帖子,可能会在截止日期前向绝望的人提供功能失调的代码。这些是令人无法接受的糟糕答案,除了 C.R。 SE,它清楚地解决了它的局限性。

因此必须在 Stack Overflow 等可靠平台上以问答形式解决此问题。

此问答是为了避免将来的 C beginners/intermediates 在搜索如何执行此操作时出现的废话。

如果您是初学者,请阅读算法并尝试自己实现它,而不是仅仅复制代码。

免责声明:该算法替换sandwich中的sand。如果这是一个 不良影响 (取决于您的情况),我的 会缓解这种情况。

Algorithm/Steps :

  1. 提醒用户备份他们的文件。
  2. 获取文件名.
  3. Initialise : 打开用户输入的文件进行读取,打开临时文件进行写入,测试这些文件中的任何错误 openings.If 成功,获取目标字符串和它的替代品。
  4. Parse : 遍历文件并记录其中的行数,然后 rewind() fptr.
  5. 循环 1:保持在行数范围内,从文件中获取一行。
  6. If:使用strstr()判断行是否包含目标字符串。如果是,则开始循环 2。
  7. 循环 2:当在行中找到目标字符串的任何实例时,写入 tmp a) 行的所有字符直到最后一个匹配,并且 b) 要替换的词。将最后一个匹配项的引用移动到当前匹配项的位置 + 目标字符串的长度。
  8. 在下一行之前:循环 2 的一次迭代完成后,写入从最后一场比赛到 EOL 的 any/all 个字符。移至循环 1 的下一次迭代。
  9. Else:如果在5处,发现该行没有目标字符串实例,直接写入文件。
  10. Cleanup: fclose() tmp and original file.Check if fclose(tmp) was successful- if not, throw an error and remove temp file,并完成(因为这可能表明 tmp 未按预期写入,例如,如果磁盘已满)。 Else rename() tmp file to filename of original,如果失败(如在 Windows 系统中,文件不能重命名为已经存在的文件名),remove() original 并重试,即使那样失败,抛出错误并放弃。
  11. 结束:(可选)如果没有失败则向用户打印一条'success'消息。

代码:

#include <stdio.h>
#include <string.h>
#define MAX_W 501
#define MAX_L 2001
#define MAX_F 261

void eat(void); //clears stdin

int main(){
    printf("\nKeep a backup of your file in case of undesirable effects.\n");
    char frep[MAX_F]; printf("\n Filename : "); scanf("%260[^\n]",frep);eat();
    // stores fname in frep[], clears stdin
    FILE * rep = fopen(frep,"r");FILE * tmp = fopen("Temp.Ctt","w");
    // opens file for reading and tmp for writing
    if(rep==NULL||tmp==NULL){
        //NULL is returned if fopen fails.
        perror("\nError ");
    }
    else{
        char target[MAX_F]; printf("\n Target : "); scanf("%500[^\n]",target);eat(); // gets target word
        char replace[MAX_F]; printf("\n Replacement : "); scanf("%500[^\n]",replace);eat();// gets its replacement
        long long lncnt=1;
        while(1){ //parsing file to find number of lines.
            int test = fgetc(rep);
            if(test==EOF)
                break;
            else if(test=='\n'||test=='[=10=]')
                lncnt++;
        }
        rewind(rep);
        //brings pointer back to beginning of file
        char line[MAX_L], *p; int i;
        // line buffer, pointer to position of match, counter i; pointer Last Writing Point (initially points to first element of line[] )
        for(i=1;i<lncnt;i++){
            //while within no. of lines in file
            fgets(line,MAX_L,rep);char * LWP = &line[0];
            //takes line from file 'rep' and stores in line[]
            while((p=strstr(LWP,target))!=NULL){
                // while any matches exist from last writing point
                int x = p-LWP;
                // no. of chars between Last Writing Point and match.
                fprintf(tmp,"%.*s",x,LWP);//writes all chars between last match and current match
                fputs(replace,tmp);
                LWP = p + strlen(target);
                // moves the LWP forward by length of target, so that it points to next valid character.
            }
            fputs(LWP,tmp);
        }
        fclose(rep); int chk =fclose(tmp);
        if(chk==EOF){
            remove("Temp.Ctt"); perror("\nFailed ");
        }
        else{
            if(rename("Temp.Ctt",frep)==0)
                printf("\nSucess.\n\nReplaced any instances of \"%s\" with \"%s\".\n",target,replace);
            else{
                remove(frep);
                if(rename("Temp.Ctt",frep)==0)
                    printf("\nSucess.\n\nReplaced any instances of \"%s\" with \"%s\".\n",target,replace);
                else{
                    remove("Temp.Ctt"); perror("\nFailed ");
                }
            }
        }
    }
    return 0;
}
void eat()
{
    int eat;while ((eat = getchar()) != '\n' && eat != EOF);
}

如果还有任何疑问,请在下方发表评论。

  1. 从文件中读取一行到缓冲区
  2. 使用 strstr() 搜索下一次出现的“字符串”。
  3. 如果找到,print/write 找到“字符串”之前的所有字节
    1. Print/write 替换字符串。
    2. 跳过缓冲区的 strlen("the sting") 字节,并在 2
    3. 处重复
  4. 打印缓冲区的剩余部分。
  5. 重复 1
#define LINEBUF_LEN 2001

int replace(FILE *in, FILE *out, const char *str, const char *replace) {

    size_t strl;
    char line[LINEBUF_LEN];

    strl = strlen(str);
    while (fgets(line, sizeof(line), in)) {
        char *y, *x = line;
        while ((y = strstr(x, str))) {
            fwrite(x, 1, y - x, out);
            fputs(replace, out);
            x = y + strl;
        }
        fputs(x, out);
    }

    return !feof(in);
}

我的 取代了 'phrases' 或 'strings' 最技术和最严格的词义。但是,正如声明的那样,它还将替换目标 的一部分另一个 string/word/phrase - 例如,'the'在“他们”中。

如果您想替换“Bob's”中的“Bob”,那就是您的选择。但是,除非小心使用,否则它可能太强大并且具有破坏性。

此答案替换 'words'(单词 = 十进制值 32 到 126 的 ASCII 字符字符串,以任何白色 space 字符结尾)。即,只有 word 'is' 被改变,而不是 'This'.

里面的 'is'

Algorithm/Steps :

0. , 1. & 2. 与第一个答案相同。

3. 无限循环:

i) 从文件中获取一个字符。

ii) 如果字符== EOF,中断循环。

iii) 否则如果字符是白色space字符,直接写入tmp文件。

iv) 否则取出单词,并将其放入缓冲区。使用 strcmp(),比较缓冲区和目标。

v) 如果单词相同,使用strcpy()将替换移动到缓冲区。

vi) 将缓冲区写入文件。

4.5. 与原始答案的 9. 和 10. 相同。

代码:

    #include <stdio.h>
    #include <string.h>
    #define MAX_W 501
    #define MAX_F 261
    
    void eat(void); //clears stdin
    
    int main(){
        printf("\nKeep a backup of your file in case of undesirable effects.\n");
        char frep[MAX_F]; printf("\n Filename : "); scanf("%260[^\n]",frep);eat(); // stores fname in frep[], clears stdin
        FILE * rep = fopen(frep,"r");FILE * tmp = fopen("Temp.Ctt","w");// opens file for reading and tmp for writing
        if(rep==NULL||tmp==NULL){
            // if files could not be opened
            perror("\nError ");
        }
        else{
            char target[MAX_W]; printf("\n Target : "); scanf("%500s",target);eat(); // gets target word
            char replace[MAX_W]; printf("\n Replacement : "); scanf("%500[^\n]",replace);eat();// gets its replacement
            while(1){
                int ch = fgetc(rep);
                if(ch==EOF)
                    break;
                else if(ch==' '||ch=='\t'||ch=='\n'||ch == '\r')
                    fputc(ch,tmp);// directly write whitespace chars
                else{
                    char buffer[MAX_W];
                    fseek(rep,-1,SEEK_CUR);
                    // move FILE pointer 1 byte back to read entire word, not from 2nd char onwards
                    fscanf(rep,"%500s",buffer);
                    if(strcmp(buffer,target)==0)
                        strcpy(buffer,replace);
                    fprintf(tmp,"%s",buffer);
                }
            }
            fclose(rep); int chk =fclose(tmp);
        if(chk==EOF){
            remove("Temp.Ctt"); perror("\nFailed ");
        }
        else{
            if(rename("Temp.Ctt",frep)==0)
                printf("\nSucess.\n\nReplaced any instances of \"%s\" with \"%s\".\n",target,replace);
            else{
                remove(frep);
                if(rename("Temp.Ctt",frep)==0)
                    printf("\nSucess.\n\nReplaced any instances of \"%s\" with \"%s\".\n",target,replace);
                else{
                    remove("Temp.Ctt"); perror("\nFailed ");
                }
            }
        }
    }
    return 0;
}
void eat()
{
    int eat;while ((eat = getchar()) != '\n' && eat != EOF);
}

您可以编写一个过滤器在 stdin 上执行此替换,将修改后的数据写入 stdout。您将创建一个修改后的文件,然后删除原始文件并将修改后的文件重命名或复制到原始文件名。

必须小心替换字符串,即使它出现在很长的行中,可能被 fgets() 拆分。

如果原始文件包含空字节,读取带有 fgets() 的行也将无法正常工作。一次读取一行也不允许替换包含嵌入换行符的字符串。

这是一个使用缓冲区并避免这些陷阱的版本。它采用目标字符串和可选的替换字符串作为命令行参数:

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

int main(int argc, char *argv[]) {
    char buffer[4096];
    size_t len1, len2, pos, nread;
    const char *str1, *str2;

    if (argc < 2) {
        fprintf(stderr, "usage: %s STRING [REPLACEMENT]\n", argv[0]);
        return 1;
    }

    str1 = argv[1];  // string to search and replace
    len1 = strlen(str1);
    str2 = "";       // default replacement is empty
    len2 = 0;
    if (len1 >= sizeof(buffer)) {
        fprintf(stderr, "%s: STRING too long\n", argv[0]);
        return 1;
    }
    if (len1 == 0) {      // special case empty string
        len1 = len2 = 1;  // replace 0 byte with itself
    } else
    if (argc > 2) {
        str2 = argv[2];
        len2 = strlen(str2);
    }

    pos = 0;
    while ((nread = fread(buffer + pos, 1, sizeof(buffer) - pos, stdin)) != 0) {
        size_t start = 0, i = 0, end = pos + nread;
        while (i + len1 <= end) {
            if (buffer[i] == *str1 && !memcmp(buffer + i, str1, len1)) {
                fwrite(buffer + start, 1, i - start, stdout);
                fwrite(str2, 1, len2, stdout);
                start = i += len1;
            } else {
                i++;
            }
        }
        fwrite(buffer + start, 1, i - start, stdout);
        memmove(buffer, buffer + i, end - i);
        pos = end - i;
    }
    fwrite(buffer, 1, pos, stdout);
    return 0;
}