You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Linux tail函数实现问题:输出末尾出现多余乱码

解决自定义tail程序输出末尾乱码及行计数错误的问题

你的自定义tail程序确实存在两个核心问题:字节计数逻辑错误和字符串未终止导致的乱码,以下是具体分析和修复方案:

问题分析

  1. 字符串未终止导致乱码:
    你用栈数组char tail[bytes];存储读取的文件内容,但文件内容本身并不包含C字符串所需的'\0'终止符。printf("%s")会一直读取内存直到遇到'\0',这就会读取到栈上的垃圾数据,表现为末尾的乱码(比如“�E0V”)。

  2. 行计数逻辑错误:
    在get_bytes函数中,bytes++是在回退文件指针后执行的。当找到第n个换行符时,会直接break,导致这个换行符本身没有被计入bytes。比如当执行./tail -1 filename时,如果文件最后一行以'\n'结尾,程序会返回bytes=0,最终读取0字节,完全不输出内容。

修复方案

1. 修正字节计数逻辑

调整get_bytes函数的执行顺序,将bytes++移到读取字节之后,确保每个读取到的字节(包括换行符)都被正确计数:

int get_bytes(int lines, int fd) {
    int bytes = 0;
    char buff[1];
    while (1) {
        if (read(fd, buff, 1) == -1){
            printf("read failed\n");
            return -1;
        }
        bytes++; // 先计数当前读取的字节
        if (buff[0] == '\n') {
            lines--;
            if (lines <= 0) break;
        }
        if (lseek(fd, -2, SEEK_CUR) == -1) {
            // 到达文件开头,退出循环
            break;
        }
    }
    return bytes;
}

2. 添加字符串终止符并优化内存分配

使用动态内存分配(避免栈溢出风险),读取文件内容后手动添加'\0'终止符,同时修正输出格式避免多输出空行:

// 替换main中原来的tail缓冲区相关代码
char* tail = malloc(bytes + 1);
if (!tail) {
    printf("malloc failed\n");
    return EXIT_FAILURE;
}

if (lseek(fd, -bytes, SEEK_END) == -1) {
    printf("lseek to start of tail failed\n");
    free(tail);
    return EXIT_FAILURE;
}

ssize_t read_bytes = read(fd, tail, bytes);
if (read_bytes == -1){
    printf("read failed\n");
    free(tail);
    return EXIT_FAILURE;
}
tail[read_bytes] = '\0'; // 添加C字符串终止符
printf("%s", tail); // 不再额外加\n,避免多空行
free(tail);

3. 其他小优化

  • 修正lseek的错误提示,避免重复;
  • 确保文件描述符和动态内存都被正确释放,防止资源泄漏。

修正后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int get_lines(char** argv);
int get_bytes(int lines, int fd);

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("ungültige Anzahl an args\n");
        return EXIT_FAILURE;
    }
    int lines = get_lines(argv);
    if (lines == -1) return EXIT_FAILURE;

    // open file
    char* path = argv[2];
    int fd = open(path, O_RDONLY);
    if (fd == -1) {
        printf("file konnte nicht geöffnet werden oder existiert nicht\n");
        return EXIT_FAILURE;
    }

    // get stat of file
    struct stat infos;
    if (stat(path, &infos) == -1) {
        printf("stat failed\n");
        return EXIT_FAILURE;
    }

    // set offset to last byte
    if (lseek(fd, -1, SEEK_END) == -1) {
        printf("lseek to last byte failed\n");
        close(fd);
        return EXIT_FAILURE;
    }

    // determine number of bytes corresponding to number of lines
    int bytes = get_bytes(lines, fd);
    // printf("lines: %d\nbytes: %d\n", lines, bytes);
    if (bytes == -1) {
        close(fd);
        return EXIT_FAILURE;
    }

    // set offset to beginning of tail and save tail of file in buffer tail
    char* tail = malloc(bytes + 1);
    if (!tail) {
        printf("malloc failed\n");
        close(fd);
        return EXIT_FAILURE;
    }

    if (lseek(fd, -bytes, SEEK_END) == -1) {
        printf("lseek to start of tail failed\n");
        free(tail);
        close(fd);
        return EXIT_FAILURE;
    }

    ssize_t read_bytes = read(fd, tail, bytes);
    if (read_bytes == -1){
        printf("read failed\n");
        free(tail);
        close(fd);
        return EXIT_FAILURE;
    }
    tail[read_bytes] = '\0';
    printf("%s", tail);
    
    free(tail);
    close(fd);
    return 0;
}

int get_lines(char** argv) {
    // cast string without - to int
    char* substr = &argv[1][1];
    int lines = atoi(substr);
    if (lines == 0) {
        printf("-n mit n = Anzahl Zeilen\n");
        return -1;
    }
    return lines;
}

int get_bytes(int lines, int fd) {
    int bytes = 0;
    char buff[1];
    while (1) {
        if (read(fd, buff, 1) == -1){
            printf("read failed\n");
            return -1;
        }
        bytes++;
        if (buff[0] == '\n') {
            lines--;
            if (lines <= 0) break;
        }
        if (lseek(fd, -2, SEEK_CUR) == -1) {
            // n bytes was bigger than bytes of file
            break;
        }
    }
    return bytes;
}

测试验证

现在执行./tail -4 tail.c,输出末尾不会再出现乱码,且能正确打印最后4行内容。

内容的提问来源于stack exchange,提问作者Simon Rechermann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 20:57:41