【Linux實踐系列】:匿名管道收尾+完善shell外殼程序

🔥 本文專欄: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外殼程序的全部過程了,那么希望讀者也可以自己去實現一下,那么我下一期會繼續更新匿名管道的相關內容,并且來動手實現一個簡易的進程池,那么我會持續更新,希望你多多關注,如果本文有幫組到你的話,還請多多三連加關注哦,你的支持就是我最大的動力!

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75626.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75626.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75626.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C++初學】課后作業匯總復習(七) 指針-深淺copy

1、 HugeInt類:構造、、cout Description: 32位整數的計算機可以表示整數的范圍近似為&#xff0d;20億到&#xff0b;20億。在這個范圍內操作一般不會出現問題&#xff0c;但是有的應用程序可能需要使用超出上述范圍的整數。C可以滿足這個需求&#xff0c;創建功能強大的新的…

【C++】 —— 筆試刷題day_16

刷題_day16&#xff0c;繼續加油啊 一、字符串替換 題目解析 這道題是一道簡單的字符題目&#xff0c;題目給我們一個字符串A&#xff0c;和n表示A字符串的長度&#xff0c;再給出一個字符數組arg&#xff0c;m表示arg中是數據個數。 然我們在字符串A中找到%s然后替換成arg中的…

n8n 本地部署及實踐應用,實現零成本自動化運營 Telegram 頻道(保證好使)

n8n 本地部署及實踐應用&#xff0c;實現零成本自動化運營 Telegram 頻道&#xff08;保證好使&#xff09; 簡介 n8n 介紹 一、高度可定制性 二、豐富的連接器生態 三、自托管部署&#xff08;本地部署&#xff09; 四、社區驅動 n8n 的部署 一、前期準備 二、部署步…

flutter 桌面應用之系統托盤

系統托盤(Tray) 系統托盤就是狀態欄里面對應的圖標點擊菜單 主要有兩款框架 框架一句話評價tray_manager輕量、簡單、易用&#xff0c;適合常規托盤功能system_tray更底層、更強大、支持圖標/菜單/消息彈窗等更多功能&#xff0c;但復雜度更高 &#x1f9f1; 基礎能力對比 …

修改idea/android studio等編輯器快捷注釋從當前行開頭的反人類行為

不知道什么時候開始&#xff0c;idea編輯的快捷注釋開始從當前行開頭出現了&#xff0c;顯得實在是難受&#xff0c;我只想讓在當前行代碼的部份開始縮進兩個字符開始&#xff0c;這樣才會顯得更舒服。不知道有沒有強迫癥的猴子和我一樣&#xff0c;就像下面的效果&#xff1a;…

MySQL慢查詢全攻略:定位、分析與優化實戰

&#x1f680; MySQL慢查詢全攻略&#xff1a;定位、分析與優化實戰 #數據庫優化 #性能調優 #SQL優化 #MySQL實戰 一、慢查詢定位&#xff1a;找到性能瓶頸 1.1 開啟慢查詢日志 -- 查看當前配置 SHOW VARIABLES LIKE %slow_query%; -- 動態開啟&#xff08;重啟失效&…

當原型圖與文字說明完全不同時,測試要怎么做?

當測試遇上左右手互搏的需求&#xff0c;怎么辦&#xff1f; "這個彈窗樣式怎么和文檔寫的不一樣&#xff1f;"、"按鈕位置怎么跑到左邊去了&#xff1f;"——根據Deloitte的調查&#xff0c;62%的項目存在原型圖與需求文檔不一致的情況。這種"精神分…

關于量化交易在拉盤砸盤方面應用的部分思考

關于“砸盤”的深層解析與操盤邏輯 ??一、砸盤的本質與市場含義?? ??砸盤??指通過集中拋售大量籌碼導致價格快速下跌的行為&#xff0c;其核心目標是??制造恐慌、清洗浮籌或實現利益再分配??。不同場景下的砸盤含義不同&#xff1a; ??主動砸盤&#xff08;操控…

【項目管理】第12章 項目質量管理-- 知識點整理

項目管理-相關文檔,希望互相學習,共同進步 風123456789~-CSDN博客 (一)知識總覽 項目管理知識域 知識點: (項目管理概論、立項管理、十大知識域、配置與變更管理、績效域) 對應:第6章-第19章 第6章 項目管理概論 4分第13章 項目資源管理 3-4分第7章 項目…

一個好看的圖集展示html頁面源碼

源碼介紹 一個好看的圖集展示html頁面源碼&#xff0c;適合展示自己的作品&#xff0c;頁面美觀大氣&#xff0c;也可以作為產品展示或者個人引導頁等等 源碼由HTMLCSSJS組成&#xff0c;記事本打開源碼文件可以進行內容文字之類的修改&#xff0c; 雙擊html文件可以本地運行…

2021第十二屆藍橋杯大賽軟件賽省賽C/C++ 大學 B 組

記錄刷題的過程、感悟、題解。 希望能幫到&#xff0c;那些與我一同前行的&#xff0c;來自遠方的朋友&#x1f609; 大綱&#xff1a; 1、空間-&#xff08;題解&#xff09;-字節單位轉換 2、卡片-&#xff08;題解&#xff09;-可以不用當組合來寫&#xff0c;思維題 3、直…

LabVIEW 中 JSON 數據與簇的轉換

在 LabVIEW 編程中&#xff0c;數據格式的處理與轉換是極為關鍵的環節。其中&#xff0c;將數據在 JSON 格式與 LabVIEW 的簇結構之間進行轉換是一項常見且重要的操作。這里展示的程序片段就涉及到這一關鍵功能&#xff0c;以下將詳細介紹。 一、JSON 數據與簇的轉換功能 &am…

藍橋杯大模板

init.c void System_Init() {P0 0x00; //關閉蜂鳴器和繼電器P2 P2 & 0x1f | 0xa0;P2 & 0x1f;P0 0x00; //關閉LEDP2 P2 & 0x1f | 0x80;P2 & 0x1f; } led.c #include <LED.H>idata unsigned char temp_1 0x00; idata unsigned char temp_old…

通過HTTP協議實現Git免密操作的解決方案

工作中會遇到這樣的問題的。 通過HTTP協議實現Git免密操作的解決方案 方法一&#xff1a;啟用全局憑據存儲&#xff08;推薦&#xff09; 配置憑證存儲? 執行以下命令&#xff0c;讓Git永久保存賬號密碼&#xff08;首次操作后生效&#xff09;&#xff1a; git config --g…

Java常見面試問題

一.Liunx 二.Java基礎 1.final 2.static 3.與equals 三.Collection 1.LIst 2.Map 3.Stream 四、多線程 1.實現方法 2.線程池核心參數 3.應用場景 五、JVM 1.堆 2.棧 六、Spring 1.面向對象 2.IOC 3.AOP 七、Springboot 1.自動裝配 八、SpringCloud 1.Nacos 2.seata 3.ga…

【藍橋杯】第十六屆藍橋杯 JAVA B組記錄

試題 A: 逃離高塔 很簡單&#xff0c;簽到題&#xff0c;但是需要注意精度&#xff0c;用int會有溢出風險 答案&#xff1a;202 package lanqiao.t1;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWrit…

PyTorch Tensor維度變換實戰:view/squeeze/expand/repeat全解析

本文從圖像數據處理、模型輸入適配等實際場景出發&#xff0c;系統講解PyTorch中view、squeeze、expand和repeat四大維度變換方法。通過代碼演示對比不同方法的適用性&#xff0c;助您掌握數據維度調整的核心技巧。 一、基礎維度操作方法 1. view&#xff1a;內存連續的形狀重…

Kubernetes nodeName Manual Scheduling practice (K8S節點名稱綁定以及手工調度)

Manual Scheduling 在 Kubernetes 中&#xff0c;手動調度框架允許您將 Pod 分配到特定節點&#xff0c;而無需依賴默認調度器。這對于測試、調試或處理特定工作負載非常有用。您可以通過在 Pod 的規范中設置 nodeName 字段來實現手動調度。以下是一個示例&#xff1a; apiVe…

即時編譯器(JIT)的編譯過程是什么?

1. 觸發編譯 JIT編譯的觸發基于熱點代碼檢測&#xff0c;主要通過兩種計數器&#xff1a; ? 方法調用計數器&#xff1a;統計方法被調用的次數&#xff08;默認閾值&#xff1a;C1為1,500次&#xff0c;C2為10,000次&#xff09;。 ? 回邊計數器&#xff1a;統計循環體的執行…

Java基礎:集合List、Map、Set(超詳細版)

集合體系概述 Collection常用方法 補充&#xff1a;addAll() Collection的遍歷方式 迭代器 增強for&#xff08;空集合可以&#xff0c;null不可以&#xff09; lambda 集合對象存儲對象原理 遍歷方式的區別 List集合 特點、特有方法 遍歷方式 &#xff08;同上&#xff09…