一、前言
來制作一個簡易的 [Shell 命令]行解釋器。
首先這是與 Shell 的互動::

用下圖的[時間軸]來表示事件的發生次序。其中時間從> > 左向右。shell 由標識為 sh 的方塊代表,它隨著時間的流逝從左向右移動。shell 從用戶讀入字符串 "ls"。shell 建立一個新的進程,然后在那個進程中運行 ls 程序并等待那個進程結束。

然后 shell 讀取新的一行輸入,建立一個新的進程,在這個進程中運行程序 并等待這個進程結束。所以要寫一個 shell,需要循環以下過程:
1. 獲取命令行
2. 解析命令行
3. 建立一個子進程(fork)
4. 替換子進程(execvp)
5. 父進程等待子進程退出(wait)
二、準備工作
1.輸出提示符

這里的提示字符為用戶名 @主機名 當前路徑# 直接打印出來作為提示所用
printf("用戶名@主機名當前路徑#");
這里沒有 n,會有緩沖區的問題,類似于我們之前所說的進度條所遇到的問題,可以用 fflush(stdout) 刷新緩沖區。
2. 輸入和獲取命令
輸入
我們需要輸入一連串命令,其中可能出現空格,所以不能使用 gets 函數,需要用到 fgets 函數,同時,可以定義一個 lineCommand[NUM] 數組
#defineNUM1024 charlineCommand[NUM]; char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin); assert(s!=NULL);
但是打印的時候卻多換了一行,這是我們把 n 也讀取到了,直接進行處理即可, 清除最后一個 n
lineCommand[strlen(lineCommand)-1]=0;
可以通過打印看看效果和測試是否有 BUG
printf("test:%s
",lineCommand);

獲取
輸入之后,我們自然需要去進行獲取,我們需要分割命令行,這個地方用 strtok。把字符串切割成若干個子串:
strtok: 第一次直接傳遞參數,第二次則必須傳 NULL。且在最終 strtok 會返回 NULL。


3.shell 運行原理
同時,在理解一下 shell 的運行原理:shell 內部提取命令行做分析,然后調用 exec. shell 執行命令必須通過創建子進程,如果不創建子進程會把我們所有的 shell 全部替換,所以執行命令時一般磁盤上的程序必須創建子進程。
4. 內建命令
我們在運行自己寫的 shell 的時候,發現輸入 cd … 輸入 cd path 等命令時發現路徑并沒有改變!

沒有發生改變是因為自己寫的 shell 執行很多命令都要 fork() 創建子進程,讓子進程執行的 cd,子進程有自己的工作目錄,所以更改的子進程的目錄,子進程執行完畢,繼續用的是父進程,既 shell,并沒有影響父進程,所以并沒有改變。
對于 cd, 我們可以采用內建命令:不需要創建子進程執行,讓 shell 自己執行命令,稱為內建命令。本質就是執行系統接口,我們可以調用一個系統接口 chdir,可解決上述問題:


5. 替換
采用 execvp 進行替換進程
pid_tid=fork(); assert(id!=-1); if(id==0) { execvp(myargv[0],myargv); exit(1); }
三、整體代碼
#include
#include
#include
#include
#include
#include
#include
#defineNUM1024
#defineOPT_NUM64
charlineCommand[NUM];
char*myargv[OPT_NUM];//指針數組
intlastcode=0;
intlastsig=0;
intmain()
{
while(1)
{
//1.輸出提示符
printf("lj@VM-8-2-centos當前路徑#");
fflush(stdout);
//2.獲取用戶輸入的命令,輸入的時候,用戶最后還輸入了
char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
assert(s!=NULL);
(void)s;//避免Linux認為s變量未使用,導致警告
//清除最后一個
;例如:abcd
lineCommand[strlen(lineCommand)-1]=0;
//printf("test:%s
",lineCommand);
//"ls-a-l-i"-->字符串分割-->"ls""-a""-l""-i"
myargv[0]=strtok(lineCommand,"");
inti=1;
if(myargv[0]!=NULL&&(strcmp(myargv[0],"ls")==0))
{
myargv[i++]=(char*)"--color=auto";
}
//如果沒有子串了,strtok會返回NULL,即myargv[end]=NULL
while(myargv[i++]=strtok(NULL,""));
//如果是cd命令,不需要創建子進程,讓shell自己執行對應的命令,本質就是執行系統接口
//像這種不需要讓我們的子進程來執行,而是讓shell自己執行的命令—內建命令
//其中echo是一個自建命令
if(myargv[0]!=NULL&&(strcmp(myargv[0],"cd")==0))
{
if(myargv[1]!=NULL)chdir(myargv[1]);
continue;
}
if(myargv[0]!=NULL&&myargv[1]!=NULL&&(strcmp(myargv[0],"echo")==0))
{
if(strcmp(myargv[1],"$?")==0)
{
printf("%d,%d
",lastcode,lastsig);
}
else
{
printf("%s
",myargv[i]);
}
continue;
}
//利用條件編譯測試是否成功
#ifdefDEBUG
for(inti=0;myargv[i];++i)
{
printf("myargv[%d]:%s
",i,myargv[i]);
}
#endif
//執行命令
pid_tid=fork();
assert(id!=-1);
if(id==0)
{
execvp(myargv[0],myargv);
exit(1);
}
intstatus=0;
pid_tret=waitpid(id,&status,0);
assert(ret>0);
(void)ret;
lastcode=(status>>8)&0xFF;
lastsig=status&0x7F;
}
return0;
}

審核編輯:湯梓紅
-
Linux
+關注
關注
88文章
11746瀏覽量
218847 -
命令行
+關注
關注
0文章
83瀏覽量
10747 -
Shell
+關注
關注
1文章
375瀏覽量
25333 -
進程
+關注
關注
0文章
211瀏覽量
14514 -
解釋器
+關注
關注
0文章
103瀏覽量
6969
原文標題:Linux 實現簡易的 Shell 命令行解釋器
文章出處:【微信號:良許Linux,微信公眾號:良許Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
在STM32實現命令行
Linux圖形界面的原理與構成和Linux命令行和vi編輯器的使用手冊
Linux桌面系統初級教程之Shell命令行操作的資料概述
Linux 命令行教程好書推薦
mini shell命令行調試工具(單片機、c語言)
Linux實現簡易的Shell命令行解釋器
評論