news 2026/9/3 2:34:07

从零手搓实现 Linux 简易 Shell:内建命令 + 环境变量 + 程序替换全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手搓实现 Linux 简易 Shell:内建命令 + 环境变量 + 程序替换全解析

从零手搓实现 Linux 简易 Shell
(内建命令 + 环境变量 + 程序替换完整版)

下面我们用 C 语言一步步实现一个极简但功能相对完整的 shell,支持:

  • 读取用户输入、解析命令行(支持参数)
  • 内建命令(cd、exit、pwd、echo、export)
  • 环境变量的读取与修改(支持P A T H 、 PATH、PATHHOME 等)
  • 通过 fork + execvp 执行外部程序
  • 管道(|)的简单支持(单级管道)
  • 前后台进程(&)
  • 基本的信号处理(Ctrl+C 不退出 shell)

目标最终效果(类似 bash 的极简版)

myshell$pwd/home/user myshell$cd/tmp myshell$echo$HOME/home/user myshell$exportMYVAR=hello myshell$echo$MYVARhello myshell$ls-l|greptxt -rw-r--r--1user user1234Feb4txtfile.txt myshell$sleep10&[1]12345myshell$

完整实现代码(约 300 行)

#define_GNU_SOURCE#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/wait.h>#include<sys/types.h>#include<signal.h>#include<errno.h>#include<limits.h>#defineMAX_INPUT1024#defineMAX_ARGS64#defineMAX_PIPES2// 支持一级管道// 全局环境变量表(我们自己维护一份副本)externchar**environ;char**my_environ=NULL;// 前台进程组 pidpid_tfg_pgid=0;// ---------------------- 辅助函数 ----------------------// 字符串 trim 两端空白voidtrim(char*str){char*start=str;char*end;// 跳过开头空白while(*start&&(*start==' '||*start=='\t'||*start=='\n'))start++;if(*start==0){*str='\0';return;}// 找到结尾end=start+strlen(start)-1;while(end>start&&(*end==' '||*end=='\t'||*end=='\n'))end--;*(end+1)='\0';// 移动到开头if(start!=str)memmove(str,start,end-start+2);}// 解析一行输入为参数数组intparse_command(char*input,char**args){intargc=0;char*token;trim(input);if(strlen(input)==0)return0;token=strtok(input," \t\n");while(token&&argc<MAX_ARGS-1){args[argc++]=token;token=strtok(NULL," \t\n");}args[argc]=NULL;returnargc;}// 查找命令是否为内建intis_builtin(constchar*cmd){returnstrcmp(cmd,"cd")==0||strcmp(cmd,"exit")==0||strcmp(cmd,"pwd")==0||strcmp(cmd,"echo")==0||strcmp(cmd,"export")==0||strcmp(cmd,"env")==0;}// 内建命令执行intbuiltin_execute(char**args){if(!args[0])return1;if(strcmp(args[0],"cd")==0){if(!args[1]){chdir(getenv("HOME"));}else{if(chdir(args[1])!=0){perror("cd");}}return1;}if(strcmp(args[0],"exit")==0){exit(0);}if(strcmp(args[0],"pwd")==0){charcwd[PATH_MAX];if(getcwd(cwd,sizeof(cwd))!=NULL){printf("%s\n",cwd);}else{perror("pwd");}return1;}if(strcmp(args[0],"echo")==0){inti=1;while(args[i]){printf("%s",args[i]);if(args[i+1])printf(" ");i++;}printf("\n");return1;}if(strcmp(args[0],"export")==0){if(args[1]){// 支持 export VAR=value 或 export VARchar*eq=strchr(args[1],'=');if(eq){*eq='\0';setenv(args[1],eq+1,1);}else{setenv(args[1],"",1);}}return1;}if(strcmp(args[0],"env")==0){char**env=environ;while(*env){printf("%s\n",*env);env++;}return1;}return0;}// 简单信号处理:Ctrl+C 只影响前台进程voidsigint_handler(intsig){if(fg_pgid>0){kill(-fg_pgid,SIGINT);}// 不退出 shell}// 执行外部命令(支持单级管道)voidexecute_external(char**args,intbackground){intpipefd[2];inthas_pipe=0;// 检测是否有管道for(inti=0;args[i];i++){if(strcmp(args[i],"|")==0){has_pipe=1;args[i]=NULL;// 分割成两个命令char**cmd2=&args[i+1];pipe(pipefd);pid_tpid1=fork();if(pid1==0){// 左命令dup2(pipefd[1],STDOUT_FILENO);close(pipefd[0]);close(pipefd[1]);execvp(args[0],args);perror("execvp left");_exit(1);}pid_tpid2=fork();if(pid2==0){// 右命令dup2(pipefd[0],STDIN_FILENO);close(pipefd[0]);close(pipefd[1]);execvp(cmd2[0],cmd2);perror("execvp right");_exit(1);}close(pipefd[0]);close(pipefd[1]);if(!background){fg_pgid=pid1;waitpid(pid1,NULL,0);waitpid(pid2,NULL,0);fg_pgid=0;}else{printf("[%d] %d\n",1,pid1);}return;}}// 无管道,普通执行pid_tpid=fork();if(pid==0){// 子进程execvp(args[0],args);perror("execvp");_exit(1);}elseif(pid>0){if(!background){fg_pgid=pid;waitpid(pid,NULL,0);fg_pgid=0;}else{printf("[%d] %d\n",1,pid);}}}// 主循环intmain(){charinput[MAX_INPUT];char*args[MAX_ARGS];intargc;// 初始化环境变量副本(可选,setenv/putenv 实际操作的是 environ)// my_environ = environ; // 如果需要自己维护可复制signal(SIGINT,sigint_handler);signal(SIGTSTP,SIG_IGN);// 忽略 Ctrl+Zprintf("欢迎使用 myshell (简易版)\n");printf("支持命令:cd, pwd, echo, export, exit, env, 外部程序, 简单管道, & 后台\n\n");while(1){charcwd[PATH_MAX];getcwd(cwd,sizeof(cwd));printf("myshell:%s$ ",strrchr(cwd,'/')?strrchr(cwd,'/'):cwd);if(!fgets(input,MAX_INPUT,stdin)){printf("\n退出\n");break;}// 去掉换行input[strcspn(input,"\n")]=0;// 解析argc=parse_command(input,args);if(argc==0)continue;intbackground=0;if(argc>1&&strcmp(args[argc-1],"&")==0){background=1;args[--argc]=NULL;}// 内建命令if(is_builtin(args[0])){builtin_execute(args);continue;}// 外部命令或管道execute_external(args,background);}return0;}

编译 & 运行

gcc -o myshell myshell.c ./myshell

功能说明与扩展点

已实现:

  • 内建:cd、exit、pwd、echo、export、env
  • 环境变量读取($PATH 等通过 getenv)
  • 外部程序执行(execvp)
  • 简单单级管道(ls | grep)
  • 后台执行(sleep 100 &)
  • Ctrl+C 只杀前台进程

可以继续扩展的方向

  1. 支持多级管道(需要多个 pipefd 数组 + 循环 fork)
  2. 支持重定向(< > >>)→ 解析时识别符号,dup2
  3. 支持环境变量展开($VAR)→ 在 parse 前替换
  4. 支持历史命令(上下箭头)→ readline 库
  5. 支持 job control(jobs、fg、bg)→ 记录后台进程列表
  6. 支持 alias

小结

这个版本大约 300 行代码,已经包含了 shell 最核心的三大能力:

  • 内建命令(直接执行)
  • 环境变量(getenv / setenv)
  • 程序替换(fork + execvp)

如果你想继续深入某个部分(例如:实现多级管道、重定向、变量展开、job control),可以告诉我,我可以继续补充对应代码和解析。

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

赶deadline必备!专科生专属AI论文网站 —— 千笔·专业论文写作工具

你是否曾在论文写作中感到力不从心&#xff1f;选题无从下手、框架混乱、文献查找困难、查重率高、格式反复出错……这些难题是否让你夜不能寐&#xff1f;作为专科生&#xff0c;面对繁重的学术任务&#xff0c;你或许更需要一个高效、专业的写作助手。千笔AI&#xff0c;正是…

作者头像 李华
网站建设 2026/9/2 23:12:21

结构方程模型:软件测试公众号内容热度的科学解析钥匙

在软件测试领域&#xff0c;公众号已成为知识分享与行业交流的核心平台。然而&#xff0c;内容热度波动大&#xff0c;从业者常困惑&#xff1a;什么主题能引爆阅读量&#xff1f;传统经验分析易带主观偏差&#xff0c;而结构方程模型&#xff08;SEM&#xff09;通过因果推理&…

作者头像 李华
网站建设 2026/9/3 0:00:10

还在找免费降ai率的网站?全网降AIGC工具大盘点,建议收藏!

说实话&#xff0c;现在的大学生太难了。不让用AI写论文是不可能的。特别是到了期末或者毕业季&#xff0c;DDL就在眼前。大家疯狂赶出了初稿。结果呢&#xff1f;拿去知网或者维普一测&#xff0c;心态崩了。论文降ai率直接飙到78%。这谁顶得住&#xff1f; 为了顺利毕业&…

作者头像 李华