🔥 本文專欄:Linux Linux實踐項目
🌸作者主頁:努力努力再努力wz
💪 今日博客勵志語錄:
人生總會有自己能力所不及的范圍,但是如果你在你能力所及的范圍盡了全部的努力,那你還有什么遺憾呢
★★★ 本文前置知識:
匿名管道
輸入以及輸出重定向
1.內容回顧
那么在上一篇文章中,我詳細介紹了父子進程或者說有血緣關系的進程之間進行進程通信的方式就是采取匿名管道來實現,那么所謂的管道,它其實是一個特殊的文件,之所以特殊是因為它不需要像普通的文件那樣寫入到磁盤中,所以我們稱管道叫做內存級文件,那么父子進程通過管道來通信,那么只能進行單向通信,如果雙方同時對管道文件進行讀寫那么必定會造成內容混亂,所以只能一個進程向管道文件中寫入,另一個進程從管道文件中讀取,那么就要求父子進程關閉各自的讀寫端,那么這就是對于上文的匿名管道的內容的一個大致的回顧,那么如果對此感到有點陌生或這樣遺忘的話,可以看我介紹匿名管道的那一期博客
2.Linux的匿名管道指令
那么在我們初學Linux的時候,我們學過其中一個指令,那么就是我們可以將一個進程本來向標準輸出文件也就是顯示器文件寫入的內容給重定向到另一個進程的標準輸入中,那么就是通過一個豎線“|”來連接,那么此時豎線左邊的指令或者說進程要輸出的內容就會寫入給豎線右邊的進程中
cmd1 | cmd2
那么這個豎線“|”其實就是匿名管道,因為Linux下所有的指令本質上就是一個進程,那么指令的執行就是創建一個進程,那么這些指令的對應的進程的創建則都是由命令行解釋器也就是shell外殼程序調用fork接口來完成,所以這些指令的對應的父進程都是shell外殼程序,那么意味著這些指令對應的進程之間關系便是是兄弟進程,而我們知道父子進程或者具有血緣關系進程之間通信的方式就是通過匿名管道來實現的,那么fork系統接口創建子進程的方式是通過拷貝父進程的task_struct結構體,然后修改其中的部分屬性得到子進程自己的task_struct結構體,其中就包括文件描述表,意味著子進程會繼承父進程打開的文件
那么對于此時“|”隔開的指令,那么它們也會繼承父進程也就是shell外殼程序的文件描述表,那么shell外殼程序再調用fork之前,那么首先便會調用pipe接口來創建一批管道,然后再調用fork創建出來的這幾個指令對應的子進程,其便會繼承之前創建出的管道,這些指令對應的子進程本來是向顯示器文件寫入,由于匿名管道的存在,此時就會被輸出重定向寫入到管道文件中,然后另一個讀取的進程本來從標準輸入也就是鍵盤文件中讀取,那么此時就會從輸入重定向到管道文件中讀取,那么這就是shell外殼程序的匿名管道來讓子進程通信的一個大致實現
3.完善shell外殼程序
(1).整體框架
那么有可匿名管道的概念之后,我們可以完善之前寫的shell外殼程序,使其支持匿名管道,那么在實現shell外殼程序之前,那么首先我們的腦海中要有一個大致的一個實現的框架,也就是代碼整體的一個實現思路,梳理出這個shell外殼程序所涉及的各個模塊,那么有了模塊之后,再來談每一個模塊實現的一個具體的細節
1.獲取用戶輸入的字符串
那么首先shell外殼程序的第一個環節就是獲取用戶輸入的字符串,那么由于這里我們引入了管道,那么意味著用戶輸入的字符串的內容可能不只一個基本的指令,而是可能包含多個基本指令然后中間用管道(|)隔開,所以這里我們首先獲取到用戶輸入的字符串,將其保存在一個臨時的數組中
2.解析字符串
那么獲取到用戶輸入的字符串之后,那么下一步便是解析字符串,那么解析字符串我們可以用一個函數來完成這個模塊,那么其中要實現的功能就是統計用戶輸入的基本指令的個數以及分割出每一個基本指令的指令部分以及參數部分并且還要判斷每一個基本指令是否具有輸入以及輸出重定向,那么這一個環節是我們這個shell外殼程序實現的難點之一,因為這個模塊要干的工作有很多不僅要分割出基本指令還要分割基本指令對應的指令部分以及參數部分等等,而且其中還有一個很大的坑,那么我們在下文談其具體實現的時候,我會說道
3.判斷指令個數
那么在上一個環節我們會獲取用戶輸入的基本指令的個數,是零個還是兩個還是4個,那么這里我們就要判斷,如果是零個基本指令,那么代表用戶就只是敲了一個回車鍵,意味著沒有輸入任何相關內容,那么此時就得回到開頭的第一步,而到時候這所有的模塊會處于一個死循環的邏輯,那么這里如果指令個數為0,那么就會continue回到開頭的第一步去繼續獲取用戶下一次的輸入,那么如果用戶輸入的命令個數為1個,那么接下來我們就得判斷用戶輸入的指令是內置指令還是外部命令,那么如果是內置指令則不需要調用fork接口創建子進程來執行,而如果基本指令的個數大于1,那么用戶必定輸入了管道符進行了分割,那么這里就要執行關于管道相關的處理邏輯了
4.指令的執行
那么這里指令的執行的前提一定是有指令,也就是指令的個數大于等于1,那么指令的個數等于1,如何執行,那么上文第三個環節說過,這里就不在重復,而如果指令的個數大于1,那么必定涉及到管道,那么這里我們就得專門創建一個函數模塊來執行各個基本指令以及要實現這些基本指令通過管道來傳輸內容,也就是通過輸出重定向來實現
那么這就是我們shell外殼程序的一個大致的執行思路,那么梳理清楚之后,我們就要落實到每一個模塊如何去實現,以及要注意的一些實現細節
(2).具體模塊實現
1.獲取用戶輸入的字符串
那么平常我們獲取用戶的鍵盤輸入的話,采取的函數都是scanf函數來獲取用戶的輸入,但是這里由于用戶在輸入每一個基本指令的時候,那么有意思的手動添加空格將基本指令的指令部分以及參數部分隔開,而scanf讀取到空格時,便停止讀取輸入了,那么無法獲取用戶完整輸入的字符串,所以這里我們不能采取scanf函數來獲取用戶的輸入,而是應該采取fgets函數,那么它會讀取包括空格的連續字符,遇到換行符停止讀取,所以這里在實現的時候,注意一定不能選擇scanf來讀取
那么這里我們調用fegts函數則是從標準輸入流中,讀取指定長度的字符串,然后保存到一個臨時的temp字符數組中
char temp[MAX_SIZE];
if(fgets(temp,sizeof(temp),stdin)==NULL)
{
perror("fgets");
continue;
}
2.解析字符串
而我們知道用戶輸入的基本指令可能不止一個,因為有管道的存在,并且我們還得獲取其中每一個基本指令的指令部分已經參數部分,以及它是否有輸入或者輸出重定向,如果有的話,我們還得保存輸入以及輸出重定向的目標文件的文件名,那么所以我們得采取一個數據結構來保存每一個基本指令以及其對應的這各種屬性,所以這里可以通過一個對象或者結構體,其中封裝一個字符指針數組 _ argv,那么字符指針數組每一個元素都是一個指向字符串的字符指針,對應該基本指令的指令部分以及參數部分的字符串,然后就是一個包括指令部分以及參數部分的參數總個數 _ argc,以及一個輸入以及輸出重定向的標記位_check_redir,那么它是一個int類型的變量,如果該基本指令涉及到輸入重定向,那么它的值就會被設置為1,追加重定向其值會被設置為2,而如果是輸入重定向其值會被設置為3,而沒有輸入以及輸出重定向,那么它的值就是0,而該對象或者結構體還會封裝一個字符指針filename,指向一個字符串,如果有輸入或者輸出重定向,那么它保存的就是輸入以及輸出重定向的目標文件的文件名
而這里我是采取c++的類來實現的:
class order{public:char* _argv[MAX_SIZE];int _argc=0;int _check_redir=0;char* filename=nullptr;};
然后再字符串解析之前,我會先創建一個固定大小的vector數組,其中每一個元素是order類,通過vector來保存每一個基本指令以及其對應的這各種屬性,那么創建完然后交給字符串解析函數去進行初始化
而對于字符串解析函數模塊內部的實現,那么首先我們需要調用strtok函數,那么以管道文件符(|)作為分隔符來分割出一個個的基本指令,那么這些基本指令我們先保存在臨時的字符指針數組中,那么在這個過程中我們順便可以統計基本指令的個數,并且該函數調用結束就返回基本指令的個數交給外部主函數接收
int pipe_string(std::vector<order>& order_array,char temp[]){int cmdnum=0;char* tmp[MAX_SIZE];char* token=strtok(temp,"|");while(token!=NULL&&cmdnum<MAX_SIZE){tmp[cmdnum]=token;cmdnum++;token=strtok(NULL,"|");}for(int i=0;i<cmdnum;i++){get_string(order_array[i],tmp[i]);}return cmdnum;}
接著我們在對每一個基本指令進行分割,其中會專門創建一個函數get_string來實現這個內容,并且其會接收一個vector數組中的一個order對象,然后調用strtok函數,以空格作為分割符,將分割出來的指令部分以及參數部分保存到order對象中的_ argv字符指針數組中,其中注意 _ argv的最后一個參數要設置為NULL,因為exec函數在遍歷這個字符指針數組的時候,會遍歷到NULL為止結束
接下來還會判斷是否有輸入以及輸出重定向符號,那么就會涉及到遍歷這個_ argv數組,并且是從后往前遍歷,因為我們輸入以及輸出重定向規定是重定向符號后面的字符串是重定向的文件名,那么其中我們會調用strcmp函數來匹配參數部分的字符串是否是重定向符號,斌哥注意我們只能從倒數第二個位置開始往前匹配,因為倒數第一個位置的值是NULL,最后根據匹配的重定向符號來初始化order對象的 _chekc _redir變量
其中還要注意的是,重定向符號以及后面重定向的目標文件并不是指令的有效內容,那么我們得手動將指向這兩部分的字符指針設置為NULL,但是在設置為NULL之前,一定要保存重定向的目標文件的文件名也就是初始化order對象的filename成員變量,并且由于重定向的符號以及文件名不是有效指令內容,所以最后在判斷重定向結束之后,我們要再最后統計指令部分以及參數部分的總個數,那么其中就是從前往后遍歷,因為重定向符號的字符指針已經被設置為空了,那么我們采取一個while循環的邏輯,每次遍歷是order對象的_ argc加一,直到遍歷帶 _ agrv的字符指針為NULL結束
//解析基本指令
void get_string(order& a,char* temp_order)
{
int len=strlen(temp_order);
if(len>0&&temp_order[len-1]=='\n')
{
temp_order[len-1]='\0';
}
char* token=strtok(temp_order," ");
int argc=0;
while(token!=NULL&&argc<MAX_SIZE)
{
a._argv[argc++]=token;
token=strtok(NULL," ");
}
a._argv[argc]=NULL;
a._argc=0;
for(int i=argc-2;i>=0;i--)
{
if(strcmp(a._argv[i],">")==0)
{
a._check_redir=1;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],">>")==0){ a._check_redir=2;if(i+1<argc){a.filename=a._argv[i+1];a._argv[i]=NULL;a._argv[i+1]=NULL;}break; }else if(strcmp(a._argv[i],"<")==0){a._check_redir=3;if(i+1<argc){a.filename=a._argv[i+1];a._argv[i]=NULL;a._argv[i+1]=NULL;}break;}}while(a._argv[a._argc]!=NULL&&a.argc<MAX_SIZE){a._argc++;}}
注意這里有一個坑,你會發現我這里在實現pipe_string函數的時候,那么我采取的方式是會先分割出所有的基本指令,然后將其保存在一個臨時的字符指針數組中,分割完所有的基本指令在對每一個基本指令進行進一步的解析,而并沒有采取分割出一個基本指令之后就直接將這個基本指令解析然后初始化vector數組中的一個order對象:
while(token!=NULL&&cmdnum<MAX_SIZE){get_string(order_array[i],token);cmdnum++;token=strtok(NULL,"|");}
那么首先給出一個答案,這種方式肯定是錯誤的,那么為什么是錯誤的?
那么這就和strtok函數有關,因為strtok函數再被調用的時候,那么內部為維護一個靜態的指針,指向要分割的字符串的下一個位置的起始位置,那么此時我們最開始在pipe_string調用strtok函數,然后基本指令還沒分割完,就接著在get_string函數中又再一次調用strtok函數,那么此時它會覆蓋之前的靜態指針,然后指向新的字符串的起始位置開始往后分割,等到get_string調用結束,那么此時strtok內部維護的靜態的指針已經指向了字符串結尾,那么此時它會返回NULL,那么根據此時while循環的循環調節,那么會導致直接退出循環,不會繼續分割基本指令,那么你會發現無論你輸入多少個帶有管道的基本指令,最終只會保存一個基本指令,也就是第一個基本指令,那么我之前就是這么做的,可謂把我給坑慘了,那么這里提醒讀者不要跳進這坑里面,那么這是非常關鍵的細節
3.判斷指令個數
那么接下來就是判斷指令的個數,如果指令的個數為0,那么就會繼續回到起始位置處,而如果指令個數為1,那么我們就得再判斷它是否為內建指令,那么所謂的內建指令,就是該指令的內容和父進程所處的運行環境有關,比如cd指令,cd指令的內容是要更換用戶或者說是父進程bash所處的工作目錄,那么如果你交給子進程,那么子進程會有自己獨立的一份環境變量,那么它的工作目錄的更改不影響父進程,所以像這樣的指令就是內置指令,一定得交給父進程來完成,那么我們就得準備一個全局的字符指針數組來保存內置指令對應的字符串,而order對象中的_argv字符指針數組的第一個元素便是指令部分,那么我們便會去和保存內置指令的字符指針數組中的每一個元素依次去匹配看是否為內置指令,那么這個過程會涉及調用strcmp函數,并且將其封裝到check _ order函數中
//判斷內置指令bool check_order(order& a){for(int i=0;in_cmd[i]!=NULL;i++){if(strcmp(a._argv[0],in_cmd[i])==0){return true;}}return false;}
那么如果調用check_order函數返回為true,那么就意味著其是內置指令,那么便會由父進程去親自執行,那么所謂的內置指令,那么意味著該指令的內容會被封裝為了一個模塊化的函數,那么我這里在實現的時候只涉及到cd以及pwd兩個內置指令,那么讀者可以試著嘗試添加更多的內置指令,那么內置指令的對應的函數調用,那么我將其封裝到了in_order_complete函數中
void in_order_complete(order& a){if(strcmp(a._argv[0],"cd")==0){if(a._argc==2){if(chdir(a._argv[1])!=0){perror("chdir");return;}}else{std::cout<<"the order is not right"<<std::endl;return;}}if(strcmp(a._argv[0],"pwd")==0){char cwd[1024];if(getcwd(cwd,sizeof(cwd))!=NULL){if(a._check_redir==1||a._check_redir==2){if(a._check_redir==1){int _fd=open(a.filename,O_CREAT|O_WRONLY,0666);if(_fd<0){perror("open");return;}int n=write(_fd,cwd,sizeof(cwd));if(n<0){perror("write");return;}}else if(a._check_redir==2){int _fd=open(a.filename,O_CREAT|O_WRONLY|O_APPEND,0666);if(_fd<0){perror("open");return;}int n=write(_fd,cwd,sizeof(cwd));if(n<0){perror("write");return;}}}else{std::cout<<cwd<<std::endl;}}}
}
4.指令的執行
那么剛才指令個數為1,如何執行以及其各種細節在上文已經說明,而如果基本指令的個數大于1,那么則說明有管道,那么對于含有管道的多個基本指令的執行我們可以創建一個 pipe_order_complete函數來實現
那么其中pipe_order_complete函數模塊實現的一個思路,我們可以這樣來理解:
假設基本指令的個數為n,那么管道的個數就是n-1,那么首先我們會先調用pipe接口利用for循環來創建n-1個管道,所以這里我會定義一個pipe類,那么pipe類封裝一個長度為2的int類型的數組,然后接著我會創建一個長度為n-1的vector數組,其中每一個元素就是pipe對象,而調用pipe接口的創建的每一個管道的讀寫端的文件描述符,就保存在了對應的數組的pipe對象的長度為2的int類型的數組中
class _pipe
{public:int pipe_fd[2];
};for (int i = 0; i < cmdnum - 1; i++) {int n=pipe(pipe_array[i].pipe_fd); if (n<0) {perror("pipe");exit(EXIT_FAILURE); // 錯誤時直接退出}}
而創建完了一批管道文件,那么下一步便是創建n個進程,那么還是采取的是一個for循環的邏輯,然后其中調用fork接口,那么子進程會繼承父進程打開的文件,那么其中對于起始指令以及結尾指令要進行特殊的處理,對于起始指令來說,那么它只能向管道文件中寫入而不能讀取,因為它處于起始位置,同理對于最后一個基本來說,那么它只能從管道文件中讀取而不能寫入
而對于中間的進程來說,那么我們就得將其的標準輸入給重定向到上一個管道文件的讀端,而它的標準輸入則重定向到對應管道文件的寫端
其次注意的是如果基本指令含有輸入或者輸出重定向,那么輸入以及輸出重定向的優先級是要大于管道文件的重定向,比如
cmd1 >> log.txt | cmd2
那么對于cmd1來說,如果它會像顯示器也就是標準輸出文件中寫入的話,那么此時它會將寫入到內容重定向到log.txt中而不是管道文件中,一定要注意這個細節,那么最后對于父進程來說則是關閉其之前創建的所有的管道文件
其中注意的是,對于帶有管道文件的多個基本指令的執行,那么我們不需要在去判斷每一個基本指令是否為內置指令,因為即使是內置指令,那么它也只能在子進程中執行,因為如果內置指令還在父進程中執行,那么此時父進程也就是bash進程會向管道中讀取以及寫入,那么這些管道肯定都是子進程之間通信傳遞信息,那么和父進程沒有任何關系,并且管道文件涉及到輸入以及輸出重定向也就是關閉標準輸入以及輸出文件,那么明顯會影響父進程,因為父進程還需要從標準輸入文件中讀取用戶下一次輸入的指令以及將指令的執行結果打印到終端上,所以不管是內置還是外部都統一交給子進程來執行,所以一定要注意這個細節
那么這里對于fork創建出的每一個子進程的執行邏輯和之前執行單個基本指令的執行邏輯是一樣的,先判斷是否有輸入以及輸出重定向,沒有就重定向相應的管道文件,然后再進行進程的替換
void pipe_order_complete(int cmdnum, std::vector<order>& order_array, std::vector<_pipe>& pipe_array) {// 1. 創建所有管道for (int i = 0; i < cmdnum - 1; i++) {int n=pipe(pipe_array[i].pipe_fd); if (n<0) {perror("pipe");exit(EXIT_FAILURE); // 錯誤時直接退出}}// 2. 創建子進程處理每個命令for (int i = 0; i < cmdnum; i++) {int pid = fork();if (pid < 0) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) { // 子進程// 2.1 處理輸入重定向或前一個管道if (order_array[i]._check_redir == 3) { // 輸入重定向 <int fd_in = open(order_array[i].filename, O_RDONLY);if (fd_in < 0) {perror("open");exit(EXIT_FAILURE);}dup2(fd_in, STDIN_FILENO);close(fd_in);} else if (i > 0) { // 從上一個管道讀取輸入dup2(pipe_array[i-1].pipe_fd[0], STDIN_FILENO);close(pipe_array[i-1].pipe_fd[0]);}// 2.2 處理輸出重定向或下一個管道if (order_array[i]._check_redir == 1 || order_array[i]._check_redir == 2) { // 輸出到文件(優先級高于管道)int flags = O_WRONLY | O_CREAT;flags |= (order_array[i]._check_redir == 1) ? O_TRUNC : O_APPEND;int fd_out = open(order_array[i].filename, flags, 0666);if (fd_out < 0) {perror("open");exit(EXIT_FAILURE);}dup2(fd_out, STDOUT_FILENO);close(fd_out);} else if (i < cmdnum - 1) { // 輸出到下一個管道dup2(pipe_array[i].pipe_fd[1], STDOUT_FILENO);close(pipe_array[i].pipe_fd[1]);}// 2.3 關閉所有無關的管道端for (int j = 0; j < cmdnum - 1; j++) {if (j != i && j != i-1) {close(pipe_array[j].pipe_fd[0]);close(pipe_array[j].pipe_fd[1]);}}// 2.4 執行命令execvp(order_array[i]._argv[0], order_array[i]._argv);perror("execvp");exit(EXIT_FAILURE);}}// 3. 父進程關閉所有管道端for (int i = 0; i < cmdnum - 1; i++) {close(pipe_array[i].pipe_fd[0]);close(pipe_array[i].pipe_fd[1]);}// 4. 等待所有子進程結束for (int i = 0; i < cmdnum; i++) {wait(NULL);}}
(3).完整實現
myshell.h
#include<iostream>
#include<cstdio>
#include<cstdio>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<vector>
#include<cstring>
#include<fcntl.h>
#include<cstdlib>
#include<cstdbool>
#define MAX_SIZE 1024
const char* in_cmd[]={"cd","pwd",NULL};
class order
{public:char* _argv[MAX_SIZE];int _argc=0;int _check_redir=0;char* filename=nullptr;
};
class _pipe
{public:int pipe_fd[2];
};
void get_string(order& a,char* temp_order);
int pipe_string(std::vector<order>& order_array,char temp[]);
bool check_order(order& a);
void in_order_complete(order& a);
void pipe_order_complete(int cmdnum,std::vector<order>& order_array,std::vector<_pipe>& pipe_array);
myshell.hpp
```cpp
#include"myshell.h"
void get_string(order& a,char* temp_order)
{
int len=strlen(temp_order);
if(len>0&&temp_order[len-1]=='\n')
{
temp_order[len-1]='\0';
}
char* token=strtok(temp_order," ");
int argc=0;
while(token!=NULL&&argc<MAX_SIZE)
{
a._argv[argc++]=token;
token=strtok(NULL," ");
}
a._argv[argc]=NULL;
a._argc=0;
for(int i=argc-2;i>=0;i--)
{
if(strcmp(a._argv[i],">")==0)
{
a._check_redir=1;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],">>")==0){ a._check_redir=2;if(i+1<argc){a.filename=a._argv[i+1];a._argv[i]=NULL;a._argv[i+1]=NULL;}break; }else if(strcmp(a._argv[i],"<")==0){a._check_redir=3;if(i+1<argc){a.filename=a._argv[i+1];a._argv[i]=NULL;a._argv[i+1]=NULL;}break;}}while(a._argv[a._argc]!=NULL&&a.argc<MAX_SIZE){a._argc++;}}int pipe_string(std::vector<order>& order_array,char temp[]){int cmdnum=0;char* tmp[MAX_SIZE];char* token=strtok(temp,"|");while(token!=NULL&&cmdnum<MAX_SIZE){tmp[cmdnum]=token;cmdnum++;token=strtok(NULL,"|");}for(int i=0;i<cmdnum;i++){get_string(order_array[i],tmp[i]);}return cmdnum;}bool check_order(order& a){for(int i=0;in_cmd[i]!=NULL;i++){if(strcmp(a._argv[0],in_cmd[i])==0){return true;}}return false;}void in_order_complete(order& a){if(strcmp(a._argv[0],"cd")==0){if(a._argc==2){if(chdir(a._argv[1])!=0){perror("chdir");return;}}else{std::cout<<"the order is not right"<<std::endl;return;}}if(strcmp(a._argv[0],"pwd")==0){char cwd[1024];if(getcwd(cwd,sizeof(cwd))!=NULL){if(a._check_redir==1||a._check_redir==2){if(a._check_redir==1){int _fd=open(a.filename,O_CREAT|O_WRONLY,0666);if(_fd<0){perror("open");return;}int n=write(_fd,cwd,sizeof(cwd));if(n<0){perror("write");return;}}else if(a._check_redir==2){int _fd=open(a.filename,O_CREAT|O_WRONLY|O_APPEND,0666);if(_fd<0){perror("open");return;}int n=write(_fd,cwd,sizeof(cwd));if(n<0){perror("write");return;}}}else{std::cout<<cwd<<std::endl;}}}
}void pipe_order_complete(int cmdnum, std::vector<order>& order_array, std::vector<_pipe>& pipe_array) {// 1. 創建所有管道for (int i = 0; i < cmdnum - 1; i++) {int n=pipe(pipe_array[i].pipe_fd); if (n<0) {perror("pipe");exit(EXIT_FAILURE); // 錯誤時直接退出}}// 2. 創建子進程處理每個命令for (int i = 0; i < cmdnum; i++) {int pid = fork();if (pid < 0) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) { // 子進程// 2.1 處理輸入重定向或前一個管道if (order_array[i]._check_redir == 3) { // 輸入重定向 <int fd_in = open(order_array[i].filename, O_RDONLY);if (fd_in < 0) {perror("open");exit(EXIT_FAILURE);}dup2(fd_in, STDIN_FILENO);close(fd_in);} else if (i > 0) { // 從上一個管道讀取輸入dup2(pipe_array[i-1].pipe_fd[0], STDIN_FILENO);close(pipe_array[i-1].pipe_fd[0]);}// 2.2 處理輸出重定向或下一個管道if (order_array[i]._check_redir == 1 || order_array[i]._check_redir == 2) { // 輸出到文件(優先級高于管道)int flags = O_WRONLY | O_CREAT;flags |= (order_array[i]._check_redir == 1) ? O_TRUNC : O_APPEND;int fd_out = open(order_array[i].filename, flags, 0666);if (fd_out < 0) {perror("open");exit(EXIT_FAILURE);}dup2(fd_out, STDOUT_FILENO);close(fd_out);} else if (i < cmdnum - 1) { // 輸出到下一個管道dup2(pipe_array[i].pipe_fd[1], STDOUT_FILENO);close(pipe_array[i].pipe_fd[1]);}// 2.3 關閉所有無關的管道端for (int j = 0; j < cmdnum - 1; j++) {if (j != i && j != i-1) {close(pipe_array[j].pipe_fd[0]);close(pipe_array[j].pipe_fd[1]);}}// 2.4 執行命令execvp(order_array[i]._argv[0], order_array[i]._argv);perror("execvp");exit(EXIT_FAILURE);}}// 3. 父進程關閉所有管道端for (int i = 0; i < cmdnum - 1; i++) {close(pipe_array[i].pipe_fd[0]);close(pipe_array[i].pipe_fd[1]);}// 4. 等待所有子進程結束for (int i = 0; i < cmdnum; i++) {wait(NULL);}}
`
myshell.cpp
#include"myshell.hpp"
int main()
{
while(true)
{char cwd[1024];getcwd(cwd,sizeof(cwd));
printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),cwd);
char temp[MAX_SIZE];
if(fgets(temp,sizeof(temp),stdin)==NULL)
{
perror("fgets");
continue;
}
int cmdnum=0;
std::vector<order> order_array(MAX_SIZE);
cmdnum=pipe_string(order_array,temp);
if(cmdnum==0)
{
continue;
}
else if(cmdnum==1)
{
if(check_order(order_array[0])){ in_order_complete(order_array[0]);}else{int id=fork();if(id<0){perror("fork");continue;}if(id==0){if(order_array[0]._check_redir==1){ int _fd=open(order_array[0].filename,O_CREAT|O_WRONLY|O_TRUNC,0666);if(_fd<0){perror("open");continue;}int n=dup2(_fd,1);if(n<0){perror("dup2");continue;}}if(order_array[0]._check_redir==2){int _fd=open(order_array[0].filename,O_CREAT|O_WRONLY|O_APPEND,0666);if(_fd<0){perror("open");continue;}int n=dup2(_fd,1);if(n<0){perror("dup2");continue;}}if(order_array[0]._check_redir==3){int _fd=open(order_array[0].filename,O_CREAT|O_RDONLY,0666);if(_fd<0){perror("open");continue;}int n=dup2(_fd,0);if(n<0){perror("dup2");continue;}}execvp(order_array[0]._argv[0],order_array[0]._argv);perror("execvp");exit(EXIT_FAILURE);}int statues;int n=waitpid(id,&statues,0);if(n<0){perror("waitpid");}}}else{std::vector<_pipe> pipe_array(cmdnum-1);pipe_order_complete(cmdnum,order_array,pipe_array);}}}
4.結語
那么這就是本期關于完善shell外殼程序的全部過程了,那么希望讀者也可以自己去實現一下,那么我下一期會繼續更新匿名管道的相關內容,并且來動手實現一個簡易的進程池,那么我會持續更新,希望你多多關注,如果本文有幫組到你的話,還請多多三連加關注哦,你的支持就是我最大的動力!