C - 从文件中删除/修改一行



我有一个datas.txt文件:

格式 : 姓氏 债务偿还

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

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

我会

  1. 询问用户名和姓氏scanf,然后将它们分配给名称和姓氏变量。
  2. 它将搜索文件名姓氏,然后将债务和付款分配给债务付款变量(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. 我想删除"约翰·多伊"的记录。
  2. 我想将"约翰·多伊"的债务减少到 100 美元

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

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

编辑:PS:使用fgets,然后是sscanf(或其他标记行的方法(将为您节省很多悲伤。

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

当我们尝试将我们的想法("修改此行"、"删除该行"(转换为文件操作时,问题变得更加清晰。我们可以通过在换行符处停止来读取一行,但是根本没有命令将其切成部分;只设置结束(ftruncate(((。因此,要更改行的大小,我们需要复制其后的所有数据。这是可以完成的,但重新创建文件通常更容易。比较实现 memmove(( 的微妙之处。

这样做的传统方法有两种变体,具体取决于您可以忍受的副作用。

一种是将更新的版本写入另一个文件中,然后将其重命名((。这样做的好处是,新文件在您放置到位时已经完成,但缺点是它可能与旧文件在权限等方面不完全匹配,并且对于已经打开旧文件的其他程序不可见。如果两个程序像这样修改文件,则这是一个争用条件,因为其中一个更改被另一个更改覆盖。

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

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

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

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(在指示"就地"修改文件时使用的方法。

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

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

我通常处理这样的事情的方式是编写一个函数,该函数可以"读入"您的数据并将其存储到某种结构中。然后是一个将数据从结构写入文件的函数。

这样,您就可以操作数组中的数据。这也使您的程序更具可扩展性,可以执行排序或额外的数学运算,而这些操作无法通过写入文件顶部来完成。

例如,

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

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

然后你做什么来制作这些结构的数组并操纵它们。您将学到很多关于结构,动态内存分配的知识,并且无疑会遇到一些有趣的问题,这些问题将帮助您学习。

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

最新更新