【问题标题】:C function to insert text at particular location in file without over-writing the existing textC函数在文件中的特定位置插入文本而不覆盖现有文本
【发布时间】:2012-02-20 10:16:54
【问题描述】:

我编写了一个程序,它将文件作为输入,每当它找到长度 > 80 的行时,它都会在该文件中添加 \ 和 \n 以使其最大宽度为 80 个字符。

问题是我在长度超过 80 时使用 fseek 插入 \ 和 \n,因此它会覆盖该行中超过长度 80 的两个字符。有没有一种方法可以在不覆盖现有文本的情况下插入文本文本?

这是我的代码:-

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

int main(int argc, char *argv[])
{
  FILE *fp1,*fp2;
  int prev=0,now=0;
  char ch;
  int flag=0;
  long cur;
  fp1=fopen(argv[1],"r+");
  if(fp1==NULL){
    printf("Unable to open the file to read. Program will exit.");
    exit(0);
  }
  else{
    while((ch=fgetc(fp1))!=EOF){
      if(ch!=' ' && ch!='\n'){
        now=now+1;
      }
      else{
        if(now>=80){
            fseek(fp1,cur,SEEK_SET);
            fputc('\\',fp1);
            fputc('\n',fp1);
            now=0;
            continue;
        }
        if(ch=='\n'){
          flag=0;
          now=0;
          continue;
          }
        else{
          prev=now;
          cur=ftell(fp1);
        }
        now=now+1;
      }
    }
  }
  fclose(fp1);
  return 0;
}

要运行它,您需要执行以下操作:-

user@ubuntu$ cc xyz.c
user@ubuntu$ ./a.out file_to_check.txt

【问题讨论】:

  • 不,没有办法将数据插入文件中间。您需要自己处理“转移”数据,和/或写入新文件。
  • @Mat:我会将您的评论标记为“不是评论”,因为这是一个答案:)
  • @ArmenTsirunyan:这个答案已经做了几十次了......

标签: c file-io


【解决方案1】:

虽然有几种技术可以就地执行此操作,但您正在处理一个文本文件并希望执行插入操作。操作系统通常不支持将文本文件插入作为文件系统原语,而且它们没有理由这样做。

最好的方法是打开你的文件进行读取,打开一个新文件进行写入,复制插入点之前的文件部分,插入数据,复制其余的,然后将新文件移到旧文件上。

这是一种常见的技术,它有一个目的。如果出现任何问题(例如您的系统),您仍然拥有原始文件,并且可以稍后重复交易。如果您启动流程的两个实例并使用特定模式,则第二个实例能够检测到事务已经启动。通过独占文件访问,它甚至可以检测事务是否被中断或仍在运行。

与直接在原始文件上执行的任何技术相比,这种方式更不容易出错,并且被所有那些传统工具(如 sed)使用,即使您要求它们就地工作 (sed -i)。另一个好处是,您始终可以在覆盖之前将原始文件重命名为带有备份后缀的文件(sed 也提供了这样的选项)。

即使您的程序正在编写一个全新的版本并且不使用原始文件,同样的技术也经常用于配置文件。不久之后,许多互联网杂志声称 ext4 意外地将配置文件截断为零长度。这正是因为一些应用程序在系统被强制关闭时保持配置文件打开和截断。这些应用程序经常在数据准备好之前篡改原始配置文件,甚至在不同步它们的情况下保持打开状态,这使得数据损坏的窗口变得更大。

TL;DR 版本:

当您重视数据时,在准备好替换数据之前不要销毁它。

【讨论】:

    【解决方案2】:

    不,无法将字符插入现有文件。您将需要使用第二个文件来执行此操作。

    【讨论】:

    • 这并不完全正确。您可以再次使用 seek()、fread()、seek() 和 fwrite()。当然,因此尾巴必须适合记忆。
    • @ckruse:那,如果fwrite() 由于某种原因没有完成,你也有破坏原始文件的风险。因此,我不认为这种方法是可行的解决方案。
    • 尾部不必适合内存。您可以搜索到末尾并将字符从末尾向后移动。如果您不关心它的病态速度,那么单字符缓冲区就足够了。在实践中,您需要一个至少为几 KB 的缓冲区,但您绝对不需要将整个尾部存储在内存中。
    • 你只需要和你插入的字节一样多的内存;这就是必须预读多少才能允许插入数据而不破坏它。
    • 实际上,@R.. 的一字节缓冲区是正确的,如果您可以接受病态缓慢和破坏性失败的风险:您可以一次从有效负载中注入一个字符。
    【解决方案3】:

    这是我用于这种事情的功能:

    int finsert (FILE* file, const char *buffer) {
    
        long int insert_pos = ftell(file);
        if (insert_pos < 0) return insert_pos;
    
        // Grow from the bottom
        int seek_ret = fseek(file, 0, SEEK_END);
        if (seek_ret) return seek_ret;
        long int total_left_to_move = ftell(file);
        if (total_left_to_move < 0) return total_left_to_move;
    
        char move_buffer[1024];
        long int ammount_to_grow = strlen(buffer);
        if (ammount_to_grow >= sizeof(move_buffer)) return -1;
    
        total_left_to_move -= insert_pos;
    
        for(;;) {
            u16 ammount_to_move = sizeof(move_buffer);
            if (total_left_to_move < ammount_to_move) ammount_to_move = total_left_to_move;
    
            long int read_pos = insert_pos + total_left_to_move - ammount_to_move;
    
            seek_ret = fseek(file, read_pos, SEEK_SET);
            if (seek_ret) return seek_ret;
            fread(move_buffer, ammount_to_move, 1, file);
            if (ferror(file)) return ferror(file);
    
            seek_ret = fseek(file, read_pos + ammount_to_grow, SEEK_SET);
            if (seek_ret) return seek_ret;
            fwrite(move_buffer, ammount_to_move, 1, file);
            if (ferror(file)) return ferror(file);
    
            total_left_to_move -= ammount_to_move;
    
            if (!total_left_to_move) break;
    
        }
    
        seek_ret = fseek(file, insert_pos, SEEK_SET);
        if (seek_ret) return seek_ret;
        fwrite(buffer, ammount_to_grow, 1, file);
        if (ferror(file)) return ferror(file);
    
        return 0;
    }
    

    像这样使用它:

    FILE * file= fopen("test.data", "r+");
    ASSERT(file);
    
    const char *to_insert = "INSERT";
    
    fseek(file, 3, SEEK_SET);
    finsert(file, to_insert);
    
    ASSERT(ferror(file) == 0);
    fclose(file);
    

    这(正如其他人在这里提到的那样)理论上可以在出现错误时损坏文件,但这里有一些代码可以实际做到这一点......像这样就地执行通常很好,但你应该备份如果您担心的话,请归档...

    【讨论】:

      【解决方案4】:

      不,没有办法。您必须创建一个新文件或将文件内容向后移动 2 个字符。

      【讨论】:

        【解决方案5】:

        您可以将文件加载为块(在您的情况下为 80 个字符),然后附加两个字符(新行)并将内容写入另一个文件。

        【讨论】:

          【解决方案6】:

          另一个实现使用tmpfile()

          #include <stdio.h>
          #include <stdlib.h>
          #include <assert.h>
          
          FILE *tmp_buf;
          int finsert(FILE *f, const char* msg){
              fseek(tmp_buf, 0, SEEK_SET);
              fpos_t f_pos;
              assert (fgetpos(f, &f_pos)==0);
          
              char buf[50];
              while(fgets(buf, 50, f))
                  fputs(buf, tmp_buf);
          
              long tmp_buf_pos = ftell(tmp_buf);
          
              fsetpos(f, &f_pos);
              fputs(msg, f);
              fseek(tmp_buf, 0, SEEK_SET);
          
              while(--tmp_buf_pos>=0)
                  fputc(fgetc(tmp_buf), f);
          
              return ferror(f);
          }
          
          int main()
          {
              FILE *f = fopen("result.txt", "wb+");
              assert (f!=NULL);
              fputs("some text", f);
              tmp_buf = tmpfile();
              assert (tmp_buf!=NULL);
              assert(finsert(f, "another text")==0);
              fclose (f);
              perror("");
          }
          

          在 Cygwin64 中测试

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-10-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-05-08
            相关资源
            最近更新 更多