使用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; }
问题原因分析
- 进程分叉逻辑错误:第一次
fork()后生成了父进程和子进程1,接着这两个进程都执行了第二次fork(),最终产生4个进程——其中子进程1fork出的多余子进程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
相关产品推荐
相关产品推荐

