【Linux進程篇】Linux進程管理應用——虛假的shell腳本

W...Y的主頁 😊

代碼倉庫分享💕?

?


前言:我們已經了解了進程的工作原理,并且學習了進程創建、進程終止、進程等待以及進程程序替換。為了更好的鞏固這些知識,我們可以創建一個簡易的shell命令行。

目錄

做一個簡易的shell

觀察shell命令行

獲取命令行

解析命令行

執行命令行

處理內建指令

完整代碼


做一個簡易的shell

觀察shell命令行

我們要做命令行就要觀察其行為,考慮下面這個與shell典型的互動:

[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps

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

然后shell讀取新的一行輸入,建立一個新的進程,在這個進程中運行程序 并等待這個進程結束。
所以要寫一個shell,需要循環以下過程:

1. 獲取命令行
2. 解析命令行
3. 建立一個子進程(fork)
4. 替換子進程(execvp)

5. 父進程等待子進程退出(wait)?

根據這些思路,和我們前面的學的技術,就可以自己來實現一個shell了。

獲取命令行

首先我們得先創建一個命令行提示符,在Linux不同版本下命令行提示符的格式不太一樣。在這里我們使用centos os7的命令行提示符。

格式為:

[用戶名@主機名 當前路徑]$/#
$一般是普通用戶,#一般是超級用戶root

上面的用戶名、主機名、當前路徑在環境變量中都可以查詢到,我們可以使用getenv函數進行獲取:

const char* HostName()
{char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
}const char* UserName()
{char *hostname = getenv("USER");if(hostname) return hostname;else return "None";
}const char *CurrentWorkDir()
{char *hostname = getenv("PWD");if(hostname) return hostname;else return "None";
}

并且我們要獲取用戶所輸入的命令。在這里我們不能使用scanf獲取輸入,因為使用scanf遇到空格時會停止讀入。所以我們使用fgets函數獲取輸入。

 char commandline[SIZE];
// 1. 打印命令行提示符,獲取用戶輸入的命令字符串
int n = Interactive(commandline, SIZE);int Interactive(char out[], int size)
{// 輸出提示符并獲取用戶輸入的命令字符串"ls -a -l"printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] = 0; //'\0', commandline是空串的情況?return strlen(out);
}

解析命令行

因為我們最后要讓子進程進行進程程序替換,所以我們要使用enev*函數,這些函數需要將命令和拆開一步一步執行,下一步就是我們將獲取的命令行按照空格的方式拆開,放到argv的指針數組中進行保存。進程程序替換博客

我們推薦使用C語言提供的接口,strtok函數可以將一個字符串按照特定的字符進行切割。

void Split(char in[])
{int i = 0;argv[i++] = strtok(in, SEP); // "ls -a -l"while(argv[i++] = strtok(NULL, SEP)); if(strcmp(argv[0], "ls") ==0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}//main函數
Split(commandline);

執行命令行

執行命令行時為了不讓我們的本進程改變,所以要創建子進程進行替換。然后用進程程序替換函數進行替換。

void Execute()
{pid_t id = fork();if(id == 0){// 讓子進程執行命名execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); 
}

?這個簡易的shell就已經做好了,當我們使用一些代碼時就可以進行了。但是我們要注意當我們進行回車時會出現問題,所以我們得在解析命令行之前進行判斷,如果獲取的字符串個數為0,我們進行continue即可。

處理內建指令

但是還有一個問題,我們執行不了如cd + 路徑等的內建指令。就拿cd指令做演示,因為cd命令是想改變當前路徑的,但是我們使用子進程進行程序替換時,子進程進行完時就會退出沒有實際意義,我們想要的其實是bash進程的切換,所以這種內建指令不能使用子進程進行替換,我們得進行特殊處理。

?首先我們得檢測一個命令是否是內建命令,如果是返回值為1,不是內建命令返回值為0,當返回值為1時,我們不需要讓子進程進行程序替換,我們直接continue即可。這里我們以cd命令作為例子。

我們只能使用strcmp函數與內建命令cd進行判斷是否相等,如果相等我們拿去argv中的1號位置內容進行判斷,如果cd后面沒有指令直接獲取家目錄的路徑。getenv函數即可。

char *Home()
{return getenv("HOME");
}

如果有cd命令后面有路徑,我們直接使用chdir函數將更改工作目錄的路徑。

?

int BuildinCmd()
{int ret = 0;// 1. 檢測是否是內建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 執行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);return ret;}
}

但是我們運行后發現執行后沒有問題,但是在命令行提示符中的當前工作路徑卻沒有改變,也就是說我們剛才寫的獲取當前路徑的函數中獲取環境變量中的路徑沒有改變,所以我們要實時對環境變量進行更新。

我們使用getcwd函數可以獲取當前路徑,然后再修改環境變量中的路徑即可。

?更新后的代碼:

int BuildinCmd()
{int ret = 0;// 1. 檢測是否是內建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 執行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, "PWD=%s", temp);putenv(pwd);}return ret;
}

?我們的內建命令不止有cd,還有export,echo指令等待。所以我們也對這些命令做了特殊處理。這些代碼直接放在完整代碼中,想要了解的可以查看。

完整代碼

一下是簡易shell的完整代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;const char* HostName()
{char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
}const char* UserName()
{char *hostname = getenv("USER");if(hostname) return hostname;else return "None";
}const char *CurrentWorkDir()
{char *hostname = getenv("PWD");if(hostname) return hostname;else return "None";
}char *Home()
{return getenv("HOME");
}int Interactive(char out[], int size)
{// 輸出提示符并獲取用戶輸入的命令字符串"ls -a -l"printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] = 0; //'\0', commandline是空串的情況?return strlen(out);
}void Split(char in[])
{int i = 0;argv[i++] = strtok(in, SEP); // "ls -a -l"while(argv[i++] = strtok(NULL, SEP)); if(strcmp(argv[0], "ls") ==0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){// 讓子進程執行命名execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); //printf("run done, rid: %d\n", rid);
}int BuildinCmd()
{int ret = 0;// 1. 檢測是否是內建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 執行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, "PWD=%s", temp);putenv(pwd);}else if(strcmp("export", argv[0]) == 0){ret = 1;if(argv[1]){strcpy(env, argv[1]);putenv(env);}}else if(strcmp("echo", argv[0]) == 0){ret = 1;if(argv[1] == NULL) {printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}else{char *e = getenv(argv[1]+1);if(e) printf("%s\n", e);}}else{printf("%s\n", argv[1]);}}}return ret;
}int main()
{while(1){char commandline[SIZE];// 1. 打印命令行提示符,獲取用戶輸入的命令字符串int n = Interactive(commandline, SIZE);if(n == 0) continue;// 2. 對命令行字符串進行切割Split(commandline);// 3. 處理內建命令n = BuildinCmd();if(n) continue;// 4. 執行這個命令Execute();}return 0;
}

exec/exit就像call/return
一個C程序有很多函數組成。一個函數可以調用另外一個函數,同時傳遞給它一些參數。被調用的函數執行一定的操作,然后返回一個值。每個函數都有他的局部變量,不同的函數通過call/return系統進行通信。
這種通過參數和返回值在擁有私有數據的函數間通信的模式是結構化程序設計的基礎。Linux鼓勵將這種應用于程序之內的模式擴展到程序之間。如下圖

一個C程序可以fork/exec另一個程序,并傳給它一些參數。這個被調用的程序執行一定的操作,然后通過exit(n)來返回值。調用它的進程可以通過wait(&ret)來獲取exit的返回值。?


以上就是本次的全部內容,感謝大家觀看!?

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

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

相關文章

GAT1399協議分析(六)--校時

一、官方消息定義 DeviceIDType &#xff1a;GA/T1400.1,采集設備、 卡口點位、 采集系統、分析系統、視圖庫、應用平臺等設備編碼規則 TimeCorrectModeType&#xff1a; dateTime時間格式&#xff1a; TimeZone&#xff1a;時區&#xff0c;GAT1400里面沒有找到具體內容&…

臥式攪拌機:一鍵自動稱重輕松搞定

在現代化工業生產中&#xff0c;G效、精準的設備是提高生產效率、降低生產成本。近年來&#xff0c;臥式攪拌機一鍵自動稱重包裝機的出現&#xff0c;無疑為眾多行業帶來了變革。這種集攪拌、稱重、包裝于一體的智能化設備&#xff0c;以其G效、便捷、精準的特點&#xff0c;迅…

六軸機器手臂運動控制——直流伺服反饋系統設計(比賽項目計劃書+設計總結+硬件+源代碼+上位機等)

TW的硬件一直很強&#xff0c;這是難得的硬件創新比賽的優秀作品&#xff0c;資料非常完整理。 硬件設計&#xff0c;源碼&#xff0c;項目計劃書&#xff0c;甚至包含了事后的復盤總結文檔。 是不可多得的好資料。 項目系統框架圖 1. 硬件系統框架圖 (請以方塊圖形式呈現) …

C#.net MassTransit和DotNetCore.CAP區別

MassTransit和DotNetCore.CAP對比 https://github.com/MassTransit/MassTransit https://github.com/dotnetcore/CAP MassTransit和DotNetCore.CAP是兩種不同的.NET庫&#xff0c;它們在核心概念、設計目的和技術實現等方面存在差異。具體分析如下&#xff1a; 核心概念 Mas…

idea的代碼沒有提交到倉庫怎么撤回到本地?

代碼已經提交到變更列表但是還沒有push推送到倉庫上&#xff0c;可以用這個方法 點擊日志-右鍵要撤回的記錄-選擇撤銷提交 撤銷的又回到本地變更 當然你只能撤銷自己提交的&#xff0c;別人的你撤銷不了

python-題庫篇-為什么數組下標從0 開始而不是 1

為什么很多編程語言要把 0 作為第一個下標索引&#xff0c;而不是直觀的 1 呢&#xff1f; 這個問題 Dijkstra 已經解答過了&#xff0c;沒錯&#xff0c;就是你知道的 Dijkstra&#xff0c;Dijkstra 最短路徑算法&#xff0c;荷蘭語全名是 Edsger Wybe Dijkstra&#xff0c;于…

定制高溫隧道爐,如何判斷質量好壞

在現代工業生產中&#xff0c;高溫隧道爐扮演著不可或缺的角色。對于特定工藝要求&#xff0c;如陶瓷燒制、金屬熱處理等&#xff0c;定制化的高溫隧道爐更是不可或缺。然而&#xff0c;面對市場上琳瑯滿目的產品&#xff0c;如何判斷高溫隧道爐的質量好壞成為了企業決策者面臨…

js終止遞歸

終止遞歸 1. 實現目標&#xff1a;js 編寫遞歸方法 查找指定節點&#xff1b; 2. 需解決問題&#xff1a;找到所需節點后&#xff0c;遞歸不會終止&#xff0c;直到所有節點遍歷完成后才會停止&#xff0c;會消耗性能 3. 解決方案&#xff1a;優化遞歸方法&#xff0c;在找到…

解決vscode終端不顯示conda環境變量名稱問題【詳細步驟!實測可行!!】

最近在使用Visual Studio Code (VSCode) 時候&#xff0c;發現終端沒有正確顯示激活的conda環境名稱&#xff0c;搜了一下&#xff0c;找到原因&#xff0c;記錄一下&#xff0c;如果有人也遇到同樣的問題&#xff0c;可以收藏一下。 ??分別兩種情況&#xff0c;一是windows系…

一周學會Django5 Python Web開發 - Django5內置Auth認證系統-用戶登錄實現

鋒哥原創的Python Web開發 Django5視頻教程&#xff1a; 2024版 Django5 Python web開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili2024版 Django5 Python web開發 視頻教程(無廢話版) 玩命更新中~共計57條視頻&#xff0c;包括&#xff1a;2024版 Django5 Python we…

JVM學習-內存泄漏

內存泄漏的理解和分類 可達性分析算法來判斷對象是否是不再使用的對象&#xff0c;本質都是判斷一上對象是否還被引用&#xff0c;對于這種情況下&#xff0c;由于代碼的實現不同就會出現很多內存泄漏問題(讓JVM誤以為此對象還在引用&#xff0c;無法回收&#xff0c;造成內存泄…

旭日X3與英偉達Orin NX通過TCP傳輸圖片

觀前提醒&#xff1a;本文主要內容為使用Python在局域網內建立TCP連接并傳輸圖片信息&#xff0c;計算機為一塊旭日X3和一塊英偉達Orin NX。 一、什么是TCP TCP&#xff08;傳輸控制協議&#xff09;是一種可靠的、面向連接的協議&#xff0c;它確保數據包的順序傳輸和完整性…

關于教務排課的那些事

在辦學過程中&#xff0c;你是否被如下問題困擾&#xff1f; 1、排課功率低&#xff1a; 為了確保師資資源得到充分利用&#xff0c;教務教師排課要求了解每一個全職和兼職教師&#xff0c;了解每一個人的時刻組織和帶班狀況&#xff0c;因而在排課的時分需求處理很多的信息&a…

【Java數據結構】二叉樹詳解(四)

&#x1f512;文章目錄&#xff1a; 1.????前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; 2.給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先 2.1第一種思路 2.2第二種思路 3.根據一棵樹的前序遍歷與中序遍歷構造二叉樹 4.根據一棵樹的中序…

vite相關配置

1、vite開發環境和生產環境的環境變量配置 1、環境變量配置 vite本身會直接讀取為什么會讀取&#xff0c;因為含有dotenv第三方庫 &#xff0c;會直接讀取 .env文件&#xff0c; 2、css模塊化簡單處理 css:{modules:{localsConvention: "camselCaseOnly" // 打包配…

機器學習_決策樹與隨機森林

決策樹是一種常用的監督學習算法&#xff0c;既可以用于分類任務也可以用于回歸任務。決策樹通過遞歸地將數據集劃分成更小的子集&#xff0c;逐步建立樹結構。每個節點對應一個特征&#xff0c;樹的葉子節點表示最終的預測結果。構建決策樹的關鍵是選擇最佳的特征來分割數據&a…

圖文型LED顯示屏的結構與安裝

隨著科技的不斷進步&#xff0c;LED顯示屏已成為商業廣告、公共信息顯示等領域不可或缺的一部分。圖文型LED顯示屏以其獨特的優勢和多樣化的應用場景&#xff0c;受到了市場的廣泛歡迎。本文將詳細介紹圖文型LED顯示屏的結構特點、工作原理以及安裝指南。 1、圖文型LED顯示屏的…

Python項目開發實戰:看圖猜成語小程序(案例教程)

一、項目背景與概述 在現代社會,隨著智能手機的普及和移動互聯網的快速發展,移動應用(App)已經成為人們日常生活中不可或缺的一部分。看圖猜成語作為一種集知識性和娛樂性于一體的游戲,深受大眾喜愛。本項目旨在開發一個基于Python的看圖猜成語小程序,讓用戶能夠通過簡單…

跨境電商|Facebook Marketplace怎么做?

2016 年&#xff0c;Facebook打造了同名平臺 Facebook Marketplace。通過利用 Facebook 現有的龐大客戶群&#xff0c;該平臺取得了立竿見影的成功&#xff0c;每月訪問量將超過 10 億。對于個人賣家和小企業來說&#xff0c;Facebook Marketplace是一個不錯的銷貨渠道&#xf…

Java內部類、枚舉類、注解類

Java 是一種面向對象的編程語言&#xff0c;它支持多種類型的類&#xff0c;包括內部類、枚舉類和注解類 一、內部類&#xff08;Inner Class&#xff09;&#xff1a; 內部類是定義在另一個類內部的類。它可以訪問外部類的成員&#xff08;包括私有成員&#xff09;&#xff…