Ubuntu 20.04自制shell批量执行脚本出现文件不存在报错问题
问题场景
在Linux Ubuntu 20.04.4系统中,实现了一个简易shell命令解释器(核心代码见下文)。交互式执行以下命令完全正常:
ls > y cat < y | sort | uniq | wc > y1 cat y1 rm y1 ls | sort | uniq | wc rm y
但将这些命令保存到t.sh文件后,通过./a.out < t.sh批量执行时,出现错误提示:
5 5 29 5 5 29 cat: y1: No such file or directory rm: Cannot erase 'y1': no such file or directory 4 4 27 rm: Cannot clear 'y': no such file or directory
实际检查发现cat和rm命令已经正常执行,但仍弹出文件不存在的错误,且相同代码在他人环境中运行正常。
核心实现代码
void runcmd(struct cmd *cmd) { int p[2], r; int fd; int pid; char str[30] ="/bin/"; FILE * stream; struct execcmd *ecmd; struct pipecmd *pcmd; struct redircmd *rcmd; if(cmd == 0) _exit(0); switch(cmd->type){ default: fprintf(stderr, "unknown runcmd\n"); _exit(-1); case ' ': ecmd = (struct execcmd*)cmd; if(ecmd->argv[0] == 0){ _exit(0); fprintf(stderr, "exec not implemented\n"); } // Your code here ... else if(execvp(ecmd->argv[0],ecmd->argv)==-1){ ecmd->argv[0] = strcat(str,ecmd->argv[0]); if(execvp(ecmd->argv[0],ecmd->argv)==-1){ fprintf(stderr,"file not found\n"); exit(1); } } break; case '>': rcmd = (struct redircmd*)cmd; ecmd = (struct execcmd*)(rcmd->cmd); if(0 <(fd = open(rcmd->file,O_CREAT|O_WRONLY,0644))){ stream = freopen(rcmd->file,"w",stdout); runcmd(rcmd->cmd); fclose(stream); } else { fprintf(stderr,"file open error\n"); } close(fd); stream = NULL; break; case '<': rcmd = (struct redircmd*)cmd; // Your code here ... if( 0 < (fd = open(rcmd->file,rcmd->flags))) stream = freopen(rcmd->file,"r",stdin); runcmd(rcmd->cmd); fclose(stream); close(fd); } else fprintf(stderr,"file open error\n"); break; case '|': pcmd = (struct pipecmd*)cmd; // Your code here ... struct cmd * lcmd =(pcmd->left); struct cmd * rcmd =(pcmd->right); int fd[2]; if(pipe(fd)==-1){ fprintf(stderr,"pipe error"); exit(1); } pid = fork1(); if(pid == 0){ dup2(fd[1],1); close(fd[0]); runcmd(lcmd); } else{ dup2(fd[0],0); close(fd[1]); runcmd(rcmd); } break; } _exit(0); }
问题原因分析
1. 重定向处理未恢复父进程标准流
在case '>'和case '<'的重定向逻辑中,调用freopen修改了父进程的stdout/stdin,但执行完命令后没有恢复原始流。批量执行时,父进程的标准输入被重定向到t.sh,当处理cat < y时,父进程的stdin被改成了y文件,后续命令会读取y的内容而非t.sh剩余的命令,导致命令执行顺序混乱,出现文件未创建完成就被访问的情况。
2. 管道处理未等待子进程完成
在case '|'的管道逻辑中,父进程执行右侧命令后直接继续,没有调用waitpid等待子进程结束。批量执行时,父进程会提前触发后续命令(比如cat y1),而此时管道左侧的命令还在创建y1文件,导致文件尚未就绪,触发"文件不存在"的错误。交互式执行时,用户输入间隔给了子进程足够的执行时间,所以不会暴露问题。
3. case '<'存在语法错误
case '<'中的if语句未加花括号,导致runcmd(rcmd->cmd); fclose(stream); close(fd);无论open是否成功都会执行。当open失败时,stream为NULL,调用fclose(stream)会触发未定义行为,进一步加剧错误输出。
4. 文件描述符泄漏
在重定向处理中,open得到的文件描述符fd没有在freopen后及时关闭,子进程会继承这些未关闭的fd,可能导致文件状态同步异常,影响后续命令对文件的判断。
内容的提问来源于stack exchange,提问作者krprogrammer33

