在Linux下使用C语言模拟实现mybash
学习Linux也有一段时间了,但是一直也没有写过总结。
打算今天就开始写一个Linux的学习过程总结的专栏,希望可以和大家一起学习和进步。
今天就先写一个在在Linux下使用C语言模拟实现mybash。
也就是我们仿写Linux系统中的bash(命令解释器)的操作。
(注意:我们只是模拟bash的执行命令的操作,没有bash本身的功能强大。)
了解bash
首先,我们先来了解bash,bash是命令解释器,而命令分为两种。
命令:
- 内置命令:例如cd,exit,jobs等。
- 普通命令:例如ps,ls,cp,kill等。
在我们用bash实现普通命令时,本质是用把bash父进程fork一个子进程,在exec(替换)一个命令(例如:ps/ls…),如图所示:
而在实现内置命令时,其实是发生在bash内部直接进行操作的,详见后文。
代码解读
printf_info()函数
printf_info()函数:
该函数是用于打印提示符,提示符格式如下:
“用户名” + “@” + “主机名” + “:” + “当前位置” + “操作符” + “ ”。
为了更好符合Linux系统,我们还可以用printf()函数打印颜色。
下面分别介绍怎么获得用户名,主机名,当前位置,操作符。
用户名:
- 可以通过getpwuid()函数获取一个结构体,结构体中包含用户名。
代码实现:
struct passwd* p=getpwuid(id);//getpwuid()函数可以获取一个结构体,结构体中包含用户名
if(p==NULL)
{
printf("$");
fflush(stdout);//刷屏操作
return ;
}
主机名:
- 可以通过gethostname()函数获取主机名。
代码实现:
char hostname[128]={0};
gethostname(hostname,128);
当前位置:
- 可以通过getcwd()函数获取当前位置。
代码实现:
char curr_dir[256]={0};
getcwd(curr_dir,256);
操作符 :
- 正常操作是默认为“$”
- 管理员操作是默认为“#”
代码实现:
char* s ="$";
int id = getuid();
if(id== 0)//Linux系统中管理员的uid为0
{
s="#";
}
printf()函数打印颜色:
没想到吧,颜色其实可以用Linux打印出来,特别有意思。
大家可以去搜索一下printf()怎么打印颜色,这里就给大家展示一下,如图。
一个绿色的love送给大家,哈哈哈哈,是不是特别有意思呢。
为了和Linux系统本身有区别,我把用户名和主机名改为红色(默认为绿色),把当前位置改为黄色(默认为蓝色)。
代码实现:
printf("\033[1;31m%s@%s\033[0m:\033[1;33m%s\033[0m%s ",p->pw_name,hostname,curr_dir,s);
printf_info()函数总代码:
void printf_info()
{
char* s ="$";
int id = getuid();
if(id== 0)
{
s="#";
}
struct passwd* p=getpwuid(id);
if(p==NULL)
{
printf("$");
fflush(stdout);
return ;
}
char hostname[128]={0};
gethostname(hostname,128);
char curr_dir[256]={0};
getcwd(curr_dir,256);
printf("\033[1;31m%s@%s\033[0m:\033[1;33m%s\033[0m%s ",p->pw_name,hostname,curr_dir,s);
fflush(stdout);
}
get_cmd()函数
get_cmd()函数:
该函数用于获取命令。
利用strtok()函数可以把命令参数“切割”,这样我们的命令就可以带参数了。
下面就给大家介绍一下strtok()函数:
strtok()函数:
- 该函数有两个参数,第一个参数是带分隔的字符串,第二个参数是分隔符。
- 该函数返回值是分割的第一个字段
- 该函数会把每一个分隔符的地方转换为“\0”
- 转化过程如下图示例所示:
- 详细的分割如下图所示(拿cp a.c b.c举例):
代码实现:
int i=0;
char* s =strtok(buff," ");
while(s!=NULL)
{
myargv[i++]=s;
s=strtok(NULL," ");
}
get_cmd()函数总代码
char* get_cmd(char buff[],char* myargv[])
{
if(buff==NULL||myargv==NULL)
{
return NULL;
}
int i=0;
char* s =strtok(buff," ");
while(s!=NULL)
{
myargv[i++]=s;
s=strtok(NULL," ");
}
return myargv[0];
}
内置命令解决方式
前文也说了,内置命令是在bash内部实现的
其实是通过输入的命令直接和内部比较,如果巧合是内置命令,实现操作就行了。
可能还不是很清楚,我们拿exit举例。
exit
- 首先我们mybash内部的循环一定要设置为死循环(因为要不断输入命令),
- 所以exit操作就特别简单,直接break退出循环就解决了。
代码实现:
if(strcmp(cmd,"exit")==0)
{
break;
}
如果还是不清楚,我们再来实现一下cd内置命令。
cd
- cd第一个要参数不为空
- 通过chdir()函数找到位置
- 如果没有找到可以通过perror()输出错误原因
代码实现:
if(strcmp(cmd,"cd")==0)
{
if(myargv[1]!=NULL)
{
if(chdir(myargv[1])!=0)
{
perror("chdir err:");
}
}
}
全代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/wait.h>
#include<pwd.h>
#define ARGC 10
char* get_cmd(char buff[],char* myargv[])
{
if(buff==NULL||myargv==NULL)
{
return NULL;
}
int i=0;
char* s =strtok(buff," ");
while(s!=NULL)
{
myargv[i++]=s;
s=strtok(NULL," ");
}
return myargv[0];
}
void printf_info()
{
char* s ="$";
int id = getuid();
if(id== 0)
{
s="#";
}
struct passwd* p=getpwuid(id);
if(p==NULL)
{
printf("$");
fflush(stdout);
return ;
}
char hostname[128]={0};
gethostname(hostname,128);
char curr_dir[256]={0};
getcwd(curr_dir,256);
printf("\033[1;31m%s@%s\033[0m:\033[1;33m%s\033[0m%s ",p->pw_name,hostname,curr_dir,s);
fflush(stdout);
}
int main()
{
while(1)
{
printf_info();
char buff[128]={0};
fgets(buff,128,stdin);
buff[strlen(buff)-1]=0;
char* myargv[ARGC]={0};
char* cmd = get_cmd(buff,myargv);
if(cmd==NULL)//空格刷屏
{
continue;
}
if(strcmp(cmd,"exit")==0)
{
break;
}
else if(strcmp(cmd,"cd")==0)
{
if(myargv[1]!=NULL)
{
if(chdir(myargv[1])!=0)
{
perror("chdir err:");
}
}
}
else
{
pid_t pid =fork();
if(pid==-1)
{
continue;
}
if(pid == 0)
{
execvp(cmd,myargv);
printf("exec err\n");
exit(0);
}
wait(NULL);
}
}
}
下面我们上效果图:
最后
第一次写这么长的文章,很用心在写,以后也会陆续更新在Linux学习的过程。
如果有什么问题都可以评论或者私聊,我们一起讨论。
最后,如果觉得这篇文章对你有帮助的话,麻烦点点赞啦,谢谢。