在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學習的程序,
如果有什么問題都可以評論或者私聊,我們一起討論,
最后,如果覺得這篇文章對你有幫助的話,麻煩點點贊啦,謝謝,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356870.html
標籤:其他
