news 2026/6/24 14:42:29

应用--Minishell实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
应用--Minishell实现

支持后台运行的Minishell实现

一、具体代码

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> int main() { char cmd[100]; char *args[10]; // 回收已经退出的子进程(非阻塞) while (waitpid(-1, NULL, WNOHANG) > 0) { // 自动回收僵尸进程 } while (1) { printf("> "); fflush(stdout); if (fgets(cmd, sizeof(cmd), stdin) == NULL) break; // 解析命令 int i = 0; args[i] = strtok(cmd, " \t\n"); while (args[i]) args[++i] = strtok(NULL, " \t\n"); if (!args[0]) continue; if (strcmp(args[0], "quit") == 0) break; else if (strcmp(args[0], "cd") == 0) { if (args[1]) { if (chdir(args[1]) != 0) perror("cd"); } } else { pid_t pid = fork(); if (pid == 0) { // 子进程 execvp(args[0], args); perror(args[0]); exit(1); } else if (pid > 0) { // 父进程 // 这里可以立即进行非阻塞回收 int status; pid_t ret = waitpid(pid, &status, WNOHANG); if (ret == 0) { printf("命令 %s 正在后台运行 (PID: %d)\n", args[0], pid); } else if (ret == pid) { if (WIFEXITED(status)) { printf("命令执行完成,退出码: %d\n", WEXITSTATUS(status)); } } } } } // 等待所有子进程结束 while (waitpid(-1, NULL, 0) > 0); return 0; }

主要特点

  1. 支持前台命令(阻塞等待)

  2. 支持后台命令(非阻塞执行)

  3. 自动回收僵尸进程

  4. 实现了基本的shell功能

二、逐行代码解释

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> int main() { char cmd[100]; // 存储用户输入的命令 char *args[10]; // 存储解析后的参数 // 回收已经退出的子进程(非阻塞) while (waitpid(-1, NULL, WNOHANG) > 0) { // 自动回收僵尸进程 }

1.启动时清理僵尸进程

  • waitpid(-1, NULL, WNOHANG):非阻塞回收所有子进程

  • > 0:有子进程被回收

  • 循环直到没有僵尸进程

while (1) { printf("> "); fflush(stdout); // 立即刷新输出缓冲区 // 读取用户输入 if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;

2.显示提示符和读取命令

  • fgets():安全读取一行输入

  • 如果返回NULL(如按Ctrl+D),则退出循环

// 解析命令 int i = 0; args[i] = strtok(cmd, " \t\n"); // 第一次调用 while (args[i]) args[++i] = strtok(NULL, " \t\n"); // 后续调用 if (!args[0]) continue; // 空行,继续循环

3.命令解析

  • strtok():按空格、制表符、换行符分割字符串

  • 结果存储在args数组中

  • args[0]是命令名,后续是参数

if (strcmp(args[0], "quit") == 0) break; else if (strcmp(args[0], "cd") == 0) { if (args[1]) { if (chdir(args[1]) != 0) perror("cd"); } }

4.内置命令处理

  • quit:直接退出循环

  • cd:使用chdir()改变当前工作目录

  • perror("cd"):如果chdir()失败,显示错误信息

else { pid_t pid = fork(); // 创建子进程 if (pid == 0) { // 子进程 execvp(args[0], args); // 执行命令 perror(args[0]); // 如果execvp失败 exit(1); // 子进程异常退出 } else if (pid > 0) { // 父进程 - 关键:非阻塞回收 int status; pid_t ret = waitpid(pid, &status, WNOHANG);

5.执行外部命令

  • fork():创建子进程

  • 子进程:execvp()执行命令

  • 父进程:waitpid(pid, &status, WNOHANG)非阻塞检查子进程状态

if (ret == 0) { printf("命令 %s 正在后台运行 (PID: %d)\n", args[0], pid); } else if (ret == pid) { if (WIFEXITED(status)) { printf("命令执行完成,退出码: %d\n", WEXITSTATUS(status)); } } } } }

6.非阻塞回收的状态判断

  • ret == 0:子进程还在运行,转为后台运行

  • ret == pid:子进程已结束,立即回收

  • WIFEXITED(status):检查是否正常退出

  • WEXITSTATUS(status):获取退出码

// 等待所有子进程结束 while (waitpid(-1, NULL, 0) > 0); return 0; }

7.程序退出前清理

  • 阻塞等待所有子进程结束

  • 防止留下僵尸进程

三、关键知识点整理

1.非阻塞回收的三种情况

pid_t ret = waitpid(pid, &status, WNOHANG); // 情况1: ret > 0 (子进程已结束) if (ret == pid) { // 子进程已结束,可以获取状态 } // 情况2: ret == 0 (子进程仍在运行) if (ret == 0) { // 子进程还在运行,可以做其他事情 } // 情况3: ret == -1 (错误或没有子进程) if (ret == -1) { // 处理错误 }

2.后台运行与前台运行的区别

特性前台命令后台命令
wait方式阻塞等待非阻塞检查
用户交互需要等待完成立即返回提示符
示例lssleep 10 &(这里没有&符号但效果类似)
输出直接显示可能和用户输入交错

3.僵尸进程处理策略

// 策略1: 启动时清理(代码开头) while (waitpid(-1, NULL, WNOHANG) > 0); // 策略2: 命令执行后立即非阻塞回收 waitpid(pid, &status, WNOHANG); // 策略3: 退出前阻塞清理(代码结尾) while (waitpid(-1, NULL, 0) > 0);

4.这个实现的限制

  • 没有真正的后台符号&

    1. 所有命令都尝试非阻塞回收

    2. 如果命令执行很快(如ls),可能立即完成

    3. 如果命令执行慢(如sleep 5),才显示"后台运行"

  • 输出可能混乱

    1. 后台命令的输出可能与用户输入交错

    2. 没有进行输出重定向

  • 不支持管道和重定向

5.改进建议

// 添加后台运行符号&支持 if (args[i-1] && strcmp(args[i-1], "&") == 0) { args[i-1] = NULL; // 移除&符号 // 使用非阻塞回收 waitpid(pid, &status, WNOHANG); } else { // 前台命令,阻塞等待 waitpid(pid, &status, 0); } // 添加信号处理避免僵尸进程 signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD,系统自动回收

四、测试示例

# 编译运行 $ gcc -o minishell minishell.c $ ./minishell # 测试场景1: 快速命令(可能立即完成) > ls file1.txt file2.txt 命令执行完成,退出码: 0 # 测试场景2: 慢速命令(转为后台) > sleep 5 命令 sleep 正在后台运行 (PID: 12345) > # 立即显示新提示符,可以继续输入命令 # 测试场景3: 内置命令 > cd /tmp > pwd /tmp

五、学习要点

  1. 非阻塞回收的应用场景:实现后台任务、避免程序阻塞

  2. waitpid的灵活使用:通过选项控制等待行为

  3. 僵尸进程的预防:多种回收策略结合

  4. Shell的基本架构:读-解析-执行循环

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/24 3:34:25

Python自动化CATIA:从零到精通的模块化学习指南

Python自动化CATIA&#xff1a;从零到精通的模块化学习指南 【免费下载链接】pycatia 项目地址: https://gitcode.com/gh_mirrors/py/pycatia 在当今工业设计领域&#xff0c;CATIA作为高端CAD软件被广泛应用于航空航天、汽车制造等行业。然而&#xff0c;传统的手动操…

作者头像 李华
网站建设 2026/6/23 14:50:46

25、Pthreads 多线程编程全面解析

Pthreads 多线程编程全面解析 1. 线程同步规则与 Pthreads 概述 在多线程编程中,为了避免死锁等问题,需要明确的规则。例如,必须始终先获取互斥锁 A 再获取互斥锁 B。随着程序复杂度和同步需求的增加,执行这些规则会变得更加困难,因此应尽早开始并进行清晰的设计。 Lin…

作者头像 李华
网站建设 2026/6/23 14:51:27

31、Linux动态内存分配与管理全解析

Linux动态内存分配与管理全解析 1. 动态内存分配概述 在程序运行过程中,动态内存分配起着至关重要的作用。与自动变量和静态变量不同,动态内存是在运行时进行分配的,其大小在分配之前可能是未知的。当程序需要存储文件内容或处理用户输入时,由于其大小不确定,就需要动态…

作者头像 李华
网站建设 2026/6/23 16:09:01

38、时间处理函数的全面解析与应用

时间处理函数的全面解析与应用 1. 时间获取函数 1.1 time() 函数 time() 函数返回自纪元(epoch)以来经过的秒数,以此表示当前时间。如果参数 t 不为 NULL ,该函数还会将当前时间写入该指针。出现错误时,函数返回 -1(转换为 time_t 类型),并适当地设置 errno…

作者头像 李华
网站建设 2026/6/23 2:53:02

Laravel 13重大升级揭秘:多模态事件监听带来的5倍性能提升可能?

第一章&#xff1a;Laravel 13重大升级概览Laravel 13 带来了多项底层架构优化与开发者体验提升&#xff0c;进一步巩固其作为现代PHP框架领先地位。本次升级聚焦于性能增强、开发流程简化以及对最新PHP生态的深度集成。核心架构改进 框架底层对服务容器和门面系统进行了重构&a…

作者头像 李华
网站建设 2026/6/23 16:08:47

Symfony 8缓存配置全解析:从入门到生产环境优化

第一章&#xff1a;Symfony 8 缓存机制概述Symfony 8 提供了一套强大且灵活的缓存系统&#xff0c;旨在提升应用性能并降低重复计算开销。该机制不仅支持多种缓存适配器&#xff0c;还深度集成于框架核心组件中&#xff0c;如路由、模板、服务容器等&#xff0c;自动优化运行效…

作者头像 李华