自定义Minishell中Heredoc为何读取重定向输入?
Minishell Heredoc 与管道混合执行异常问题分析与修复
问题现象
开发类Bash的Minishell程序,支持管道、重定向、Here文档功能。执行cat << a | sort < a.txt时出现异常:
minishell> cat << a | sort < a.txt heredoc> this is also in a.txt this is in a.txt heredoc> a minishell>
sort命令的输出(this is in a.txt)提前混入了Heredoc的输入流程,导致交互混乱。
核心问题
代码的执行时序错误:当前实现是在命令子进程内部处理Heredoc,而主进程会同时创建管道后的所有命令子进程。这导致:
- 第一个命令(
cat <<a)的子进程还在等待Heredoc输入 - 第二个命令(
sort <a.txt)的子进程已经启动并输出文件内容 - 两个进程的IO同时与终端交互,造成输出混乱
Bash的标准行为是:先处理所有Here文档,完成输入后再创建子进程执行命令,不会出现并行交互的情况。
代码问题定位
- Heredoc处理时机错误:
ft_handle_redir在命令子进程内部调用,导致Heredoc与命令执行并行 - 无效的等待逻辑:
ft_execute_mult_cmd中的waitpid(ms->pid_heredoc, NULL, 0)在fork命令子进程前执行,但此时pid_heredoc尚未赋值,完全无效 - 全局临时文件风险:所有Heredoc共用
.heredoc文件,多Heredoc场景会出现内容覆盖
修复方案
1. 调整执行流程:先处理所有Heredoc,再执行命令
修改ft_executer,在fork任何命令子进程前,遍历所有命令完成Heredoc输入:
void ft_executer(t_minishell *ms) { int i; t_cmd *curr; i = 0; curr = ms->cmd_lst; // 先处理所有Heredoc,完成输入后再执行命令 while (curr) { int j = 0; while (curr->heredoc[j]) { curr->fd_in = ft_handle_heredoc(ms, curr, curr->heredoc[j++]); } curr = curr->next; } // 原有命令执行逻辑 curr = ms->cmd_lst; ms->n_pipes = ft_count_pipes(ms->cmd_lst); if (ms->n_pipes == 0) ft_execute_only_cmd(ms, curr, curr->cmd); else { ft_set_cmd_index(ms); ft_open_pipes(ms); while (curr) { ft_execute_mult_cmd(ms, curr, curr->cmd); curr = curr->next; } ft_close_pipes(ms); while (i < ms->n_pipes + 1) waitpid(ms->pid[i++], NULL, 0); } // 清理Heredoc临时文件 curr = ms->cmd_lst; while (curr) { if (curr->heredoc_file) unlink(curr->heredoc_file); curr = curr->next; } }
2. 修改Heredoc处理逻辑:主进程中执行,生成唯一临时文件
为每个Heredoc生成唯一临时文件,避免内容覆盖,并在主进程中完成输入:
// 生成唯一临时文件名 char *ft_gen_heredoc_filename(void) { static int count = 0; char *num_str = ft_itoa(count++); char *name = ft_strjoin(".heredoc_", num_str); free(num_str); return (name); } int ft_handle_heredoc(t_minishell *ms, t_cmd *curr, char *delimiter) { pid_t pid; int fd; char *filename = ft_gen_heredoc_filename(); pid = fork(); if (pid < 0) ft_perror(ms, E_FORK, YES); else if (pid == 0) { fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) ft_perror(ms, E_FILE, YES); char *line; while (1) { line = readline("heredoc> "); if (!line) { ft_perror(ms, E_HEREDOC, YES); break ; } if (ft_strncmp(line, delimiter, ft_strlen(line) + 1) == 0) { free(line); break ; } line = ft_expand_heredoc(ms, line); ft_putendl_fd(line, fd); free(line); } close(fd); ft_free_all(ms, YES); exit(0); } else waitpid(pid, NULL, 0); curr->heredoc_file = filename; // 记录临时文件名用于后续清理 fd = open(filename, O_RDONLY); if (fd < 0) ft_perror(ms, E_FILE, YES); return (fd); }
3. 移除无效逻辑
- 删除
ft_execute_mult_cmd中无效的waitpid调用 - 修改
ft_handle_redir,移除其中的Heredoc处理逻辑(已在主进程完成):
void ft_handle_redir(t_minishell *ms, t_cmd *curr) { int i; i = 0; while (curr->file_in[i]) curr->fd_in = ft_perror_fd(ms, curr->file_in[i++], T_FILE_IN); i = 0; while (curr->file_tr[i]) curr->fd_out = ft_perror_fd(ms, curr->file_tr[i++], T_FILE_TRUNC); i = 0; while (curr->file_ap[i]) curr->fd_out = ft_perror_fd(ms, curr->file_ap[i++], T_FILE_APPEND); dup2(curr->fd_in, STDIN_FILENO); dup2(curr->fd_out, STDOUT_FILENO); }
修复后效果
执行cat << a | sort < a.txt时,会先完成Heredoc输入,再执行cat和sort命令,输出与Bash一致:
minishell> cat << a | sort < a.txt heredoc> this is also in a.txt heredoc> a this is also in a.txt this is in a.txt minishell>
内容的提问来源于stack exchange,提问作者nobody really
相关产品推荐
相关产品推荐

