读取文件中的一行后如何更改一行?

How to change a line after read a line in file?

提问人:kang kang 提问时间:9/15/2023 更新时间:9/15/2023 访问量:50

问:

这是我的代码。阅读一行并更改它。

    char buf[SIZE];
    while (fgets(buf,SIZE,fp) != NULL)
    {
        to_upper(buf);   
        fseek(fp,-1L,SEEK_CUR);
        fputs(buf,fp);
        fseek(fp,1L,SEEK_CUR);
    }

我知道我可以创建另一个文件来实现,我想知道为什么代码不起作用?

C 文件 FSEEK

评论

0赞 Barmar 9/15/2023
当您在阅读和写作之间切换时,您必须打电话。fflush(fp)
0赞 Barmar 9/15/2023
请注意,这仅在您不更改行的长度时才有效。
4赞 Jonathan Leffler 9/15/2023
您需要在调用之前捕获当前的读取位置;然后,您需要设置该位置(您的代码倒带一个字节,而不是一行),然后写入。写入后查找将指针移动到写入的最后一个字节之外一个字节。在阅读和写作之间以及写作和阅读之间做一个寻求是很重要的。fgets()
0赞 chux - Reinstate Monica 9/15/2023
@kang康,要实现将文件转换为大写,无需逐行阅读。用于在每次迭代中更改数据块。fread()/fwrite()

答:

2赞 Jonathan Leffler 9/15/2023 #1

正如我在评论中指出的,您需要在调用之前捕获当前的读取位置;然后,您重置该位置并写入修改后的数据,并且在写入后需要某种搜索,以便可以再次读取。fgets()

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void to_upper(char *buffer)
{
    unsigned char u;

    while ((u = *buffer) != '\0')
        *buffer++ = toupper(u);
}

int main(int argc, char **argv)
{
    const char *fname = "input.txt";
    if (argc > 2)
    {
        fprintf(stderr, "Usage: %s [file]\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if (argc == 2)
        fname = argv[1];
    FILE *fp = fopen(fname, "r+");

    if (fp == NULL)
    {
        fprintf(stderr, "%s: failed to open file '%s' for read/write: %d %s\n",
                argv[0], fname, errno, strerror(errno));
        exit(EXIT_FAILURE);
    }

    char buffer[1024];
    long pos = ftell(fp);

    while (fgets(buffer, sizeof(buffer), fp) != 0)
    {
        to_upper(buffer);
        fseek(fp, pos, SEEK_SET);
        fputs(buffer, fp);
        fseek(fp, 0, SEEK_CUR);
        pos = ftell(fp);
    }

    fclose(fp);
    return 0;
}

打开成功后的错误检查是不存在的,但在此之前的检查对于安全操作至关重要。

C 标准 §7.21.5.3 fopen 函数 ¶7 说:

当使用更新模式打开文件时(“”作为上述模式参数值列表中的第二个或第三个字符),可以对关联的流执行输入和输出。但是,输出不应直接跟在输入后面,除非对函数或文件定位函数(、、或)进行干预调用,并且输入不能直接跟在输出后面,除非输入操作遇到文件末尾。+fflushfseekfsetposrewind

所示代码在写入后和下次读取之前谨慎使用“no-op”调用。fseek(fp, 0, SEEK_CUR)

1赞 Craig Estey 9/15/2023 #2

几个问题......

  1. 我们需要记住每条线的前后位置。做一个逐个字符的位置是行不通的。那是因为我们正在阅读一整行(长度不一)。fseek
  2. 虽然我们可以在这种特殊情况下这样做,但这有点危险。最好只是做.fputsfputc
  3. 请注意,代码之所以有效,是因为这样做需要一个,但在这里我们这样做不需要。read/seek/writefflushread/seek/write/seekfflush

下面是重构后的代码。它被注释:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int
main(int argc,char **argv)
{
    char buf[10000];

    --argc;
    ++argv;

    if (argc != 1) {
        fprintf(stderr,"need filename\n");
        exit(1);
    }

    FILE *fp = fopen(*argv,"r+");
    if (fp == NULL) {
        perror(*argv);
        exit(1);
    }

    long posbef = 0;

    while (1) {
        // get a line
        if (fgets(buf,sizeof(buf),fp) == NULL)
            break;

        // get starting position of next line
        long posaft = ftell(fp);

        // get first char of line and convert to uppercase
        int chr = buf[0];
        chr = toupper((unsigned char) chr);

        // seek [back] to start of current line
        fseek(fp,posbef,SEEK_SET);

        // output updated character
        fputc(chr,fp);

        // ensure the char is output (?)
        // need fflush after an output and before an input
#if 0
        fflush(fp);
#endif

        // seek to the start of the next line
        fseek(fp,posaft,SEEK_SET);

        // this is now the start position of the current line for the next
        // loop iteration
        posbef = posaft;
    }

    fclose(fp);

    return 0;
}