C语言模拟Linux命令解释程序(Linux Shell)
OS的课程实验之一,疫情原因窝在家里不想动,OS网课上的心烦,所以这个实验也没有写的特别的完善,实现了外部命令和五六条内部命令,内部命令也基本没有实现参数,如果以后有时间再尝试实现吧。
0x00 实验目的
探索、理解并掌握操作系统命令解释器的设计原理和实现机制,基于 Linux 内核进行相 应命令解释程序的设计和实现,并在 Linux 操作系统平台上加以测试验证。
0x01 实验环境
系统环境:MAC OS Catalina 10.15.3
虚拟机环境:Parallels desktop 15+Ubuntu 18.04.3 LTS
编程语言:C语言
0x02运行环境
理论上可以在Linux的各个发行版中运行
0x03实现代码
其中调用了一些系统调用函数,比如exec函数集用来实现外部命令的调用,内部命令也大部分依靠系统调用函数实现。
具体函数可以通过其他大佬的博客查找用法。我的代码中大部分内容注释已经解释了,所以下面直接给出代码,不再过多解释。
msh.h:
#include<sys/types.h>
#include<sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include<pwd.h>
#include <sys/stat.h>
msh.c:
#include "msh.h"
char cwd[80]; //当前工作目录
char *PATH[100];
int num_path=0; //环境变量数
char src_path[400]; //原环境变量
//==========================环境变量==============================
/*
分割环境变量字符串
*/
int split_path(char *buf,char **argv)
{
int i=0; //buf指针
int j=0; //字符串头位置指针
int argc=0;
while(*(buf+i)==' ') //找到第一个不为0的字符
i++;
j=i;
while(1)
{
if(*(buf+i)==':')
{
argv[argc++]=(buf+j); //将这个字串的头指针赋给
*(buf+i)='\0'; //将这个字符串的末尾加0分割
i++;
while(*(buf+i)==' ') //找到下一个不为0的字符
i++;
j=i;
}
else if(*(buf+i)=='\0')
{
argv[argc++]=(buf+j);
break;
}
else
i++;
}
return argc;
}
/*
初始化环境变量
*/
void init_path()
{
char *buf;
buf=getenv("PATH");
strcpy(src_path,buf);
num_path=split_path(src_path,PATH);
return;
}
/*
改变环境变量
*/
void export_path(char *now)
{
char *newpath[30]; //新增环境变量指针
int newnum; //新增环境变量数量
char *buf;
for(int i=0;i<num_path;i++)
PATH[i]=NULL;
buf=getenv("PATH");
strcpy(src_path,buf);
num_path=split_path(src_path,PATH);
newnum=split_path(now,newpath); //分割
if(strcmp(newpath[0],"$PATH")==0) //如果保留原环境变量,将新增加在末尾
{
for(int i=0;i<newnum-1;i++)
{
PATH[num_path+i]=newpath[i+1];
}
num_path+=(newnum-1);
}
else if(strcmp(newpath[newnum-1],"$PATH")==0) //保留原环境变量,将新增加在开头
{
for(int i=0;i<num_path;i++)
{
PATH[num_path-i+newnum-2]=PATH[num_path-1-i];
}
for(int i=0;i<newnum-1;i++)
{
PATH[i]=newpath[i];
}
num_path+=(newnum-1);
}
else //清除原环境变量,替换为新增
{
for(int i=0;i<num_path;i++)
{
PATH[i]=NULL;
}
for(int i=0;i<newnum;i++)
{
PATH[i]=newpath[i];
}
num_path=newnum;
}
// 连接新环境变量为字符串
char tmp[600];
strcpy(tmp,PATH[0]);
for(int i=1;i<num_path;i++)
{
strcat(tmp,":");
strcat(tmp,PATH[i]);
}
setenv("PATH",tmp,1); //设置新的环境变量
return;
}
char* split_pathhead(char *str)
{
int i=0;
while(*(str+i)!='=')
i++;
str=str+i+1;
return str;
}
//==========================外部变量==============================
/*
在环境变量中寻找外部命令
*/
/*
int find_file(char *argv)
{
int i=0;
char tmp[50];
strcpy(tmp,argv);
while(*(argv+i)!='\0') //找到"/",不是文件名,返回
{
if(*(argv+i)=='/')
{
return 0; //路径
}
else
{
i++;
}
}
DIR *dirp;
struct dirent *dp;
for(int i=0;i<num_path;i++)
{
dirp = opendir(PATH[i]); //打开目录指针
while ((dp = readdir(dirp)) != NULL) { //通过目录指针读目录
if(strcmp(dp->d_name,argv)==0)
{
strcpy(tmp,PATH[i]);
strcat(tmp,"/");
strcat(tmp,argv);
strcpy(argv,tmp);
(void) closedir(dirp); //关闭目录
return 1; //找到文件
}
}
(void) closedir(dirp); //关闭目录
}
return 2; //未找到
}
*/
int bulidout_commend(char **argv)
{
pid_t fpid; //fpid表示fork函数返回的值
/*
int flag=0;
char *envp[3] = { "", "TERM=console", NULL }; //环境
// 连接新环境变量为字符串
char tmp[400];
strcpy(tmp,"PATH=");
strcat(tmp,PATH[0]);
for(int i=1;i<num_path;i++)
{
strcat(tmp,":");
strcat(tmp,PATH[i]);
}
envp[0]=tmp;
char filename[20];
strcpy(filename,argv[0]);
int flag;
flag=find_file(filename);
*/
//argv[0]=filename;
fpid = fork();
if(fpid == 0)
{
if(execvp(argv[0], argv)<0)
{
printf("%s:command not found.\n",argv[0]);
exit(0);
}
}
else
wait(NULL);
return 0;
}
//==========================辅助函数==============================
/*
分割命令字符串
*/
int split(char *buf,char **argv)
{
int i=0; //buf指针
int j=0; //字符串头位置指针
int argc=0;
while(*(buf+i)==' ') //找到第一个不为0的字符
i++;
j=i;
while(1)
{
if(*(buf+i)==' ')
{
argv[argc++]=(buf+j); //将这个字串的头指针赋给
*(buf+i)='\0'; //将这个字符串的末尾加0分割
i++;
while(*(buf+i)==' ') //找到下一个不为0的字符
i++;
j=i;
}
else if(*(buf+i)=='\0')
{
argv[argc++]=(buf+j);
argv[argc]=NULL; //最后一个指针为空
break;
}
else
i++;
}
return argc;
}
//==========================内部命令==============================
/*
touch命令实现函数
*/
void touch_cmd(int argc,char **argv)
{
FILE *fp;
fp=fopen(argv[1],"w");
fclose(fp);
}
void date_cmd(int argc,char **argv)
{
time_t timep;
struct tm *up,* gp;
time (&timep);
up=gmtime(&timep);
gp=gmtime(&timep);
if(argc==1)
{
printf("%d年 %d月 %d日 %d:%d:%d UTC\n",1900+up->tm_year,1+up->tm_mon,
up->tm_mday,8+up->tm_hour,up->tm_min,up->tm_sec);
}
else if(argc==2&&strcmp(argv[1],"-u")==0)
{
printf("%d年 %d月 %d日 %d:%d:%d CST\n",1900+gp->tm_year,1+gp->tm_mon,
gp->tm_mday,gp->tm_hour,gp->tm_min,gp->tm_sec);
}
else
{
printf("date:commend error!\n");
}
return;
}
int bulidin_commend(char **argv,int argc)
{
if(strcmp("exit",argv[0])==0)
{
exit(0);
}
else if(strcmp("pwd",argv[0])==0)
{
getcwd(cwd,sizeof(cwd));
printf("%s\n",cwd);
return 0;
}
else if(strcmp("touch",argv[0])==0)
{
touch_cmd(argc,argv);
return 0;
}
else if(strcmp("cd",argv[0])==0)
{
if(chdir(argv[1])==0)
return 0;
else
{
printf("cd:no such file or directory\n");
return 0;
}
}
else if(strcmp("export",argv[0])==0)
{
argv[1]=split_pathhead(argv[1]);
export_path(argv[1]);
return 0;
}
else if(strcmp("date",argv[0])==0)
{
date_cmd(argc,argv);
return 0;
}
else if(strcmp("whoami",argv[0])==0)
{
uid_t id;
struct passwd * pa;
id=geteuid();
pa=getpwuid(id);
printf("%s\n",pa->pw_name);
return 0;
}
else if((strcmp("echo",argv[0])==0)&&(strcmp("$PATH",argv[1])==0))
{
char *nn;
nn=getenv("PATH");
printf("PATH=%s\n",nn);
return 0;
}
else
return 1; //未找到匹配命令
}
//==========================工作函数==============================
void work(char *str)
{
char *argv[5];
int argc=0;
argc=split(str,argv);
if(bulidin_commend(argv,argc)==0) //内部命令执行
{
return;
}
else
{
bulidout_commend(argv); //外部命令执行
}
return;
}
//==========================main==============================
int main()
{
char cmd[100]; //命令
init_path();
while(1)
{
getcwd(cwd,sizeof(cwd));
printf("msh_Shell:%s>",cwd);
gets(cmd); //读取输入字符串
work(cmd); //工作函数
}
return 0;
}
0x04 测试结果
1、执行效果
下面是当程序运行起来后显示的提示符等内容:
2、环境变量显示与设置
使用“echo $PATH”命令来显示当前的系统环境变量PATH的值,如下:
我们可以看一下使用“export”命令设置环境变量PATH,如下:
3、外部命令执行
下面执行外部命令ps,结果如下:
下面是外部命令ping的执行结果,如下:
4、内部命令执行
内部命令包括:“cd”,“pwd”,“whoami”,“touch”,“date”,“exit”,经测试,所有命令均成功执行,下面只展示cd命令和date命令的测试结果:
1、cd命令
2、date命令
0x05 后记
本来想多实现一些内部命令的参数,但是一是因为自己懒,二是实力还有所欠缺,因为用户输入参数时可以分开输入,也可以合起来输入,如“ls -al”这样,我发现如果一一实现的话花费时间较长,当然了我已经将所有输入分割出来了,下面就是识别问题,但是这个工程量有些大(主要是ddl到了/笑哭),所以就没有实现。如果以后有机会再实现一下吧。
当然我也发现我有一部分其实写的有些复杂了,是因为前期没有规划好每个功能的实现逻辑,在写完一部分后发现有代码冗余,这也提醒我了下次写代码的时候事先做好规划,以后改进hhh
源码已上传GitHub:源代码
想想你的文章写的特别好www.jiwenlaw.com
不错不错,我喜欢看