【问题标题】:C - Deleting / Modifiying A line From A FileC - 从文件中删除/修改一行
【发布时间】:2011-12-18 21:53:32
【问题描述】:

我有一个 datas.txt 文件:

格式:姓氏还债

bir bir 100 2 
iki iki 200 2 
eray alakese 100 5 
john doe 2000 10 

我正在学习 C 并且 我只知道简单的文件函数(fscanf、fprinf、fopen 等)

我会

  1. scanf询问用户名和姓氏,然后将它们分配给namesurname变量。
  2. 它将在文件中搜索 namesurname,然后将债务和付款分配给 debtpayment 变量(fscanf(file, "%s %s %d %d", name, surname, &debt, &payment);)
  3. 删除或修改此行

这是我的源代码。

    scanf("%s", &name);
    scanf("%s", &surname);
    file = fopen("datas.txt", "r");
    /* this fscanf() is working as expected. There is no problem. */
    fscanf(file, "%s %s %d %d", name, surname, &debt, &payment);

    /* modify and delete actions here */
    fclose(file);

例子:

  1. 我想删除“John Doe”的记录。
  2. 我想将“John Doe”的债务减少到 100 美元

【问题讨论】:

  • 这通常是相当困难的。如果您想在中间进行更改,您将无法移动文件的大部分。内存映射加上memmove 可能是最简单的选择。为什么不改用数据库(比如 sqlite)?
  • 你测试过这段代码吗?在我看来,fscanf 的行不会做你想做的事。也就是说,它将覆盖namesurname

标签: c file-io


【解决方案1】:

您不能删除/修改[*] 文本文件的单个行;唯一的解决方案是 1)创建一个新的临时文件,2)将内容复制到但不包括要修改/删除的行,3)输出修改后的行,4)复制原始文件的其余部分, 5) 用临时文件替换旧文件。

[*] 仅当修改后的行与原始行的长度相同时才能进行修改。

编辑:PS:使用 fgets,然后使用 sscanf(或其他标记行的方式)将为您省去很多麻烦。

【讨论】:

    【解决方案2】:

    这有点难做,因为 C 的文件模型继承自 Unix(它们主要是共同开发的),实际上并没有将文件定义为行列表。相反,它将一行定义为以换行符结尾的字节串,并将文件(大致)定义为长度可能有限的存储字节串,您可以在其中跳到不同的部分。这相当模糊,但请耐心等待。

    当我们尝试将我们的想法——“修改这一行”、“删除那一行”——转化为文件操作时,问题变得更加清晰。我们可以通过在换行处停止来读取一行,但是根本没有命令可以将它分成几段;仅设置结束(ftruncate())。所以要改变行的大小,我们需要复制它后面的所有数据。可以做到,但重新创建文件通常更容易。比较实现 memmove() 的微妙之处。

    执行此操作的传统方法有两种变体,具体取决于您可以忍受的副作用。

    一种是将您的更新版本写入另一个文件,然后将其 rename() 到位。这样做的好处是新文件将在您放置到位时完成,但缺点是它可能与旧文件不完全匹配,就权限等而言,它不会对其他程序可见旧的已经开了。如果两个程序像这样修改文件,这是一种竞争条件,因为其中一个更改被另一个更改覆盖。

    另一个是完全加载数据并将修改后的版本写下来。这意味着文件本身、权限和所有内容都保留在原位,但是在您保存时会有一段时间,它是新旧内容的混合。文本编辑器倾向于这样做,通常同时将旧内容保存为单独的文件,以防出现问题。

    也有一些工具可以管理副作用,例如版本化文件系统、文件锁定,甚至是为并行更改准备的库(我想到了 metakit)。大多数时候,我们将使用已经存在的工具,例如 sed -i。

    【讨论】:

      【解决方案3】:

      为了删除或更改一行,您必须“移动”它之后的所有内容。例如,考虑以下两个文件:

      bir bir 100 2           bytes 0-14
      iki iki 200 2           bytes 15-29
      eray alakese 100 5      bytes 30-49
      john doe 2000 10        bytes 50-67
      

      bir bir 100 2           bytes 0-14
      iki iki 200 2           bytes 15-29
      john doe 2000 10        bytes 30-57  <-- byte offsets have changed
      

      这当然是可以做到的,但一般来说支持起来相当复杂(你必须做很多寻找和告诉)。更常用的方法是有效地复制文件:从输入文件中读取并将所有内容打印到输出文件中,进行所需的修改。 (例如,要“删除”一行,您根本不打印该行。)然后,最后,在关闭两个文件后,您“重命名”输出文件以覆盖输入文件。这是sedperl 等命令行实用程序在指示“就地”修改文件时使用的方法。

      【讨论】:

        【解决方案4】:

        通常的做法是读取所有文件并将其全部写回临时文件,然后删除原始文件并重命名临时文件。

        /* pseudo-code!! */
        fopen();
        while (fscanf(source, ...)) {
            /* massage data */
            fprintf(temporary, ...);
        }
        fclose();
        remove(source);
        rename(temporary, source);
        

        【讨论】:

          【解决方案5】:

          我通常处理此类事情的方式是编写一个可以“读入”数据并将其存储到某种结构的函数。然后是一个将数据从结构中写入文件的函数。

          这样您就可以操作数组中的数据。这也使您的程序更易于扩展,可以执行排序或额外数学等操作,而这些操作仅通过覆盖文件顶部是无法完成的。

          例如尝试编写一个可以读入结构的函数,例如:

          struct Client
          {
              char name[255];
              double owes;
              double paid;
          }
          

          然后您所做的就是将这些结构组成一个数组并对其进行操作。 您将学到很多关于结构、动态内存分配的知识,并且毫无疑问会遇到一些有助于您学习的有趣问题。

          我的建议是也跳过 C 并选择 C++...从长远来看,使用 iostreams 而不是 *printf/*scanf 函数和向量来学习这些东西可能对你更好

          【讨论】:

          • 为什么投反对票?我错过了什么?我认为这是处理数据的更具可扩展性的解决方案
          猜你喜欢
          • 2020-12-17
          • 1970-01-01
          • 2015-02-06
          • 2012-04-24
          • 2021-07-12
          • 2018-07-08
          • 2010-10-14
          • 1970-01-01
          • 2013-05-13
          相关资源
          最近更新 更多