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

使用Pipes和Forks实现cut -c5-变体时输出行数异常问题

问题:用管道+进程复刻cut -c5-时输出行数异常

我用C语言结合管道(pipes)和进程分叉(forks)实现cut -c5-的变体程序,测试一个4行的文本文件时,程序输出了8行内容,远多于预期的4行。

测试文件内容

abcdefgh
123456789
101010101
ababababa

实际输出

«Data received through pipe: efgh
«Data received through pipe: 56789
«Data received through pipe: 10101
«Data received through pipe: ababaabcdefgh
«Data received through pipe: 56789
«Data received through pipe: 10101
«Data received through pipe: ababa

预期输出

«Data received through pipe: efgh
«Data received through pipe: 56789
«Data received through pipe: 10101
«Data received through pipe: ababa

问题代码

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

#define BUFFER_SIZE 1024

void function1(char *argv[]) {    

        pid_t pid1, pid2;
        int pipefd1[2], pipefd2[2];
        char buffer[BUFFER_SIZE];
        ssize_t byteRead;

        if (pipe(pipefd1) == -1) {    // 创建管道1
            perror("pipe1");
            return;
        }

        if (pipe(pipefd2) == -1) {    // 创建管道2
            perror("pipe2");
            return;
        }

        pid1 = fork();    // 创建子进程1
        if (pid1 == -1) {
            perror("fork1");
            return;
        }

        pid2 = fork();    // 创建子进程2
        if (pid2 == -1) {
            perror("fork2");
            return;
        }

        if (pid1 == 0) {    // 子进程1
            close(pipefd1[1]);
            dup2(pipefd1[0], STDIN_FILENO);
            close(pipefd1[0]); 

            close(pipefd2[0]);
            dup2(pipefd2[1], STDOUT_FILENO);
            close(pipefd2[1]); 

            execlp("cut", "cut", "-c5-", NULL);
            perror("exec");
            return;
        }
        else {    // 父进程分支
            close(pipefd1[0]);    // 关闭读端

            int filefd = open(argv[1], O_RDONLY);

            if (filefd == -1) {
                perror("Error opening file!");
                close(pipefd1[1]);
                return;
            } 

            memset(buffer, 0, BUFFER_SIZE);    // 使用前清空缓冲区

            while ((byteRead = read(filefd, buffer, BUFFER_SIZE)) > 0) {    // 将文件内容写入管道1写端
                write(pipefd1[1], buffer, byteRead);
            } 

            close(filefd); 
            close(pipefd1[1]);

            wait(NULL);

        }

        if (pid2 == 0) {    // 子进程2
            close(pipefd2[1]);

            while ((byteRead = read(pipefd2[0], buffer, BUFFER_SIZE)) > 0) {

                char *token = strtok(buffer, "\n");
                while (token != NULL) {
                    printf("«Data received through pipe: %s\n", token);
                    token = strtok(NULL, "\n");
                }
            } 

            close(pipefd2[0]);
            return;
        }     
        else {    // 父进程分支
            close(pipefd2[0]);    // 关闭读端
            close(pipefd2[1]);    // 关闭写端    

            wait(NULL);
        } 

} 

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

    if (argc == 2) {      
        function1(argv);
    }
    else {
        printf("Error! Incorrect syntax");
    }

    return 0;
}

问题原因分析

  1. 进程分叉逻辑错误:第一次fork()后生成了父进程和子进程1,接着这两个进程都执行了第二次fork(),最终产生4个进程——其中子进程1fork出的多余子进程2会重复读取管道内容并输出,导致行数翻倍。
  2. strtok跨缓冲区分割问题:当read读取的内容刚好截断一行时,剩余部分会留在缓冲区,下一次read的内容会直接拼接在后面,导致strtok无法正确分割,出现内容拼接错误(比如ababaabcdefgh)。

修复方案

1. 修正进程分叉逻辑

仅在原始父进程中创建子进程2,避免子进程1再次执行fork():

pid1 = fork();    
if (pid1 == -1) {
    perror("fork1");
    return;
}

if (pid1 > 0) {  // 仅原始父进程创建子进程2
    pid2 = fork();    
    if (pid2 == -1) {
        perror("fork2");
        return;
    }
}

2. 修复strtok跨缓冲区问题

维护一个剩余缓冲区,保存未处理的截断行内容,在下一次读取时拼接后再分割:

// 在function1开头添加剩余缓冲区变量
char leftover[BUFFER_SIZE] = {0};
size_t leftover_len = 0;

// 子进程2的读取逻辑修改为:
if (pid2 == 0) {    
    close(pipefd2[1]);

    while ((byteRead = read(pipefd2[0], buffer + leftover_len, BUFFER_SIZE - leftover_len)) > 0) {
        byteRead += leftover_len;
        buffer[byteRead] = '\0';  // 确保字符串终止

        char *token = strtok(buffer, "\n");
        while (token != NULL) {
            // 检查是否是被截断的行
            char *end = token + strlen(token);
            if (end == buffer + byteRead && *token != '\0') {
                // 保存剩余部分到leftover
                strncpy(leftover, token, BUFFER_SIZE - 1);
                leftover_len = strlen(token);
                leftover[leftover_len] = '\0';
                break;
            }
            printf("«Data received through pipe: %s\n", token);
            token = strtok(NULL, "\n");
        }
        if (token == NULL) {
            leftover_len = 0;
            memset(leftover, 0, BUFFER_SIZE);
        }
    } 

    // 处理最后剩余的内容
    if (leftover_len > 0) {
        printf("«Data received through pipe: %s\n", leftover);
    }

    close(pipefd2[0]);
    return;
}

3. 完善管道关闭逻辑

确保所有进程关闭未使用的管道描述符,避免管道写端未关闭导致read阻塞。

修复后的完整代码

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

#define BUFFER_SIZE 1024

void function1(char *argv[]) {    

        pid_t pid1, pid2 = -1;
        int pipefd1[2], pipefd2[2];
        char buffer[BUFFER_SIZE];
        char leftover[BUFFER_SIZE] = {0};
        size_t leftover_len = 0;
        ssize_t byteRead;

        if (pipe(pipefd1) == -1) {    
            perror("pipe1");
            return;
        }

        if (pipe(pipefd2) == -1) {    
            perror("pipe2");
            return;
        }

        pid1 = fork();    
        if (pid1 == -1) {
            perror("fork1");
            close(pipefd1[0]);
            close(pipefd1[1]);
            close(pipefd2[0]);
            close(pipefd2[1]);
            return;
        }

        if (pid1 > 0) {  // 仅原始父进程创建子进程2
            pid2 = fork();    
            if (pid2 == -1) {
                perror("fork2");
                close(pipefd1[0]);
                close(pipefd1[1]);
                close(pipefd2[0]);
                close(pipefd2[1]);
                waitpid(pid1, NULL, 0);
                return;
            }
        }

        if (pid1 == 0) {    // 子进程1
            close(pipefd1[1]);
            close(pipefd2[0]);

            dup2(pipefd1[0], STDIN_FILENO);
            close(pipefd1[0]); 

            dup2(pipefd2[1], STDOUT_FILENO);
            close(pipefd2[1]); 

            execlp("cut", "cut", "-c5-", NULL);
            perror("exec");
            exit(EXIT_FAILURE);
        }
        else if (pid2 == 0) {    // 子进程2
            close(pipefd1[0]);
            close(pipefd1[1]);
            close(pipefd2[1]);

            while ((byteRead = read(pipefd2[0], buffer + leftover_len, BUFFER_SIZE - leftover_len)) > 0) {
                byteRead += leftover_len;
                buffer[byteRead] = '\0';

                char *token = strtok(buffer, "\n");
                while (token != NULL) {
                    char *end = token + strlen(token);
                    if (end == buffer + byteRead && *token != '\0') {
                        strncpy(leftover, token, BUFFER_SIZE - 1);
                        leftover_len = strlen(token);
                        leftover[leftover_len] = '\0';
                        break;
                    }
                    printf("«Data received through pipe: %s\n", token);
                    token = strtok(NULL, "\n");
                }
                if (token == NULL) {
                    leftover_len = 0;
                    memset(leftover, 0, BUFFER_SIZE);
                }
            } 

            if (leftover_len > 0) {
                printf("«Data received through pipe: %s\n", leftover);
            }

            close(pipefd2[0]);
            exit(EXIT_SUCCESS);
        }     
        else {    // 原始父进程
            close(pipefd1[0]);
            close(pipefd1[1]);
            close(pipefd2[0]);
            close(pipefd2[1]);    

            waitpid(pid1, NULL, 0);
            waitpid(pid2, NULL, 0);
        } 

} 

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

    if (argc == 2) {      
        function1(argv);
    }
    else {
        fprintf(stderr, "Error! Incorrect syntax. Usage: %s <filename>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:54:58