操作系統實驗四 (綜合實驗)設計簡單的Shell程序

前言

因為是一年前的實驗,很多細節還有知識點我都已經遺忘了,但我還是盡可能地把各個細節講清楚,請見諒。

1.實驗目的

綜合利用進程控制的相關知識,結合對shell功能的和進程間通信手段的認知,編寫簡易shell程序,加深操作系統的進程控制和shell接口的認識。

2.實驗內容

可以使用Linux或其它Unix類操作系統,全面實踐進程控制、進程間通信的手段,編寫簡易shell程序要求如下:

1. 學習Shell,系統編程,實現一個基本的Shell。

2. shell是Linux等系統中的一個命令解釋器, 它接受輸入的命令, 解釋之后與操作系統進行交互. 在Linux終端Terminal輸入的指令就是被shell接收的。在shell中實現輸入輸出。

3. 在自己編寫的Shell中 實現bash的基本指令包括 cd ,ls 管道等指令

3.實驗的內容與過程

實驗前需要掌握的知識點:

在實驗前,我們應該先明白shell有以下幾個功能:

實現一個命令解析的程序

命令包含內部命令、外部命令和非法命令

內部命令包含:使用幫助的help命令,打印內容echo,目錄切換cd,退出程序exit或q

外部命令包含:系統命令,在$PATH下的命令

非法命令:找不到的命令

Shell的工作流程主要如下:

①打印提示符:可參照bash提示符,如用戶名@主機名,或者自定義提示符,如myshell >

②接收用戶輸入的命令:按行讀取內容

③解析用戶輸入的命令:解析行內容,按照空格來分隔成字符串數組

④執行命令:執行命令并打印命令結果到終端

⑤循環第一步

初步框架:

根據shell的工作流程,我們不難得出代碼的初步框架:

根據上述代碼框架,我們先編寫出自己的框架。

分析:

getcwd函數用于獲取當前工作目錄。因為shell的命令輸入是一直運行的,所以我們用while(1)來確保其不斷運行。接著在while中有三個自定義函數,分別為:print_promt(用來顯示命令行提示符:打印用戶名和當前工作目錄)、read_line(用來讀取用戶輸入的命令)、parse_cmd(用來處理用戶輸入的命令)。接下來我就解釋一下這三個自定義函數的功能。

print_promt函數:

print_promt函數:(打印的用戶名一定要記得改)

分析:

這個函數的主要功能為打印命令行提示符,打印的內容主要為用戶名和當前的工作目錄,分別用藍色字體和綠色字體輸出。

打印結果:

read_line函數:

分析:

這個函數的主要功能為讀取用戶輸入的命令,通過fgets函數將命令寫入全局變量command中,如果讀入失敗的話,則退出程序。

Parse_command函數:

分析:

這個函數主要用來判斷用戶輸入的命令是什么命令。當有輸入命令時,用自定義函數check來判斷,如果返回值為1則說明當前命令為內部命令,調用handleInternalCommand這個自定義函數來處理內部命令。如果在command里面查找到有字符‘|’則說明是一個帶有管道的命令,那么就調用executeCommandWithPipes這個自定義函數來處理命令。如果上述兩種情況都不滿足,則說明是外部命令,便調用自定義函數executeExternalCommand來處理外部命令。接下來就對這幾個自定義函數進行解釋。

Check函數:

分析:

這個函數主要用來判斷命令是否為內部命令,如果是則返回1,否則返回0。(命令可以自行添加)

HandleInternalCommand函數:

分析:

這個函數主要用來處理內部命令。因為我實現了三種內部命令(help、exit和cd)。如果判斷出輸入的命令為help,則打印出我們的提示信息。如果判斷出輸入的命令為exit,則退出程序。如果判斷輸入的命令為cd,則將路徑存放起來,改變當前工作目錄并保存。如果這三個命令都不是,則輸出無效命令的提示消息。

Executeexternalcommand函數:

分析:

這個函數主要用來執行外部命令。如果我們判斷當前輸入命令為外部命令時調用該函數。在這個函數中,我們先創建一個子進程,在子進程中,我們分割出命令,然后調用execvp函數去執行外部命令。如果執行時出錯,則說明是無效命令,并輸出相應的提示,一直等到子進程結束。

executeCommandWithPipes函數:

分析:

這個函數主要是執行帶有管道的命令。首先將命令按照管道符號“|”分割成多個子命令(由于分割的技巧,我的管道可以識別字符‘|’有空格和沒有空格的情況,下面會展示),接著根據子命令個數創建相應個數的管道。接著在管道數組中創建子進程(跟執行外部命令時的道理類似)。在執行子進程時,我們要判斷該子進程是第幾個子進程:如果不是第一個子命令,則將管道的輸入重定向到上一個子命令的輸出;如果不是最后一個子命令,則將管道的輸出重定向到下一個子命令的輸入。(這兩個過程可以通過調用dup2函數實現),接著關閉所有管道文件描述符并且執行子命令,一直等到所有子進程都結束了才算真正的執行完成了。注意,一定要關閉所有的管道文件描述符。

將以上的代碼結合在一起就能實現一個簡單的shell。至此,本次實驗已經成功完成了。

4.完整代碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>#define MAX_COMMAND_LENGTH 100
#define MAX_ARGUMENTS 10
#define MAX_PIPES 10#define RED "\e[0;31m"
#define L_RED "\e[1;31m"
#define GREEN "\e[0;32m"
#define L_GREEN "\e[1;32m"
#define BLUE "\e[0;34m"   //正常設置
#define L_BLUE "\e[1;34m" //藍色線條粗一些(0為正常,1為粗體)
#define END "\033[0m"void handleInternalCommand(char *command);
void executeExternalCommand(char *command);
void executeCommandWithPipes(char *command);char *inner_commmand[] = {"help", "exit", "cd"}; // 內部命令
char current_directory[MAX_COMMAND_LENGTH];      // 當前工作目錄
char command[MAX_COMMAND_LENGTH];    //存放輸入命令//判斷是否為內部命令
int check(char *command)
{for (int i = 0; i < 3; i++){if (i == 2){if (strncmp(command, "cd ", 3) == 0)return 1;}if (strcmp(command, inner_commmand[i]) == 0)return 1;}return 0;
}// 輸出命令行提示符
void print_prompt()
{printf("\e[1;34msztu202100202016\033[0m:\e[1;32m%s\033[0m$ ", current_directory);
}void read_line()
{// 讀取用戶輸入的命令if (fgets(command, sizeof(command), stdin) == NULL){// 讀取失敗,退出程序exit(0);}// 刪除末尾的換行符command[strcspn(command, "\n")] = '\0';
}void parse_cmd()
{if (strlen(command) > 0){if (check(command) == 1){// 內部命令handleInternalCommand(command);}else if (strstr(command, "|") != NULL){// 帶有管道的命令executeCommandWithPipes(command);}else{// 外部命令executeExternalCommand(command);}}
}// 內部命令的處理函數
void handleInternalCommand(char *command)
{if (strcmp(command, "help") == 0){printf("這是一個簡單的shell程序。\n");printf("支持的命令:\n");printf("  help - 顯示幫助信息\n");printf("  exit - 退出myshell\n");printf("  cd [目錄] - 改變當前工作目錄\n");printf("  author:Horizon\n");}else if (strcmp(command, "exit") == 0){exit(0);}else if (strncmp(command, "cd ", 3) == 0){// 獲取目標目錄char *targetDir = &command[3];// 改變當前工作目錄if (chdir(targetDir) != 0){printf("無法改變目錄:%s\n", targetDir);}else{getcwd(current_directory, sizeof(current_directory)); // 更新當前工作目錄}}else{printf("無效命令:%s\n", command);}
}// 執行外部命令
void executeExternalCommand(char *command)
{pid_t pid = fork();if (pid < 0){// fork() 出錯printf("無法創建子進程。\n");return;}else if (pid == 0){// 子進程char *args[MAX_ARGUMENTS];// 將命令分割成參數int argIndex = 0;char *token = strtok(command, " ");while (token != NULL && argIndex < MAX_ARGUMENTS - 1){args[argIndex] = token;token = strtok(NULL, " ");argIndex++;}args[argIndex] = NULL;// 執行外部命令execvp(args[0], args);// execvp() 只在出錯時返回printf("無效命令:%s\n", command);exit(0);}else{// 等待子進程結束int status;waitpid(pid, &status, 0);}
}// 執行帶有管道的命令
void executeCommandWithPipes(char *command)
{char *pipes[MAX_PIPES];int pipeCount = 0;// 將命令按照管道符號 "|" 分割成多個子命令char *token = strtok(command, "|");while (token != NULL && pipeCount < MAX_PIPES){pipes[pipeCount] = token;token = strtok(NULL, "|");pipeCount++;}// 創建管道int pipefds[2 * pipeCount];for (int i = 0; i < pipeCount; i++){if (pipe(pipefds + 2 * i) < 0){perror("無法創建管道");exit(1);}}// 執行子命令for (int i = 0; i < pipeCount; i++){pid_t pid = fork();if (pid < 0){// fork() 出錯perror("無法創建子進程");exit(1);}else if (pid == 0){// 子進程// 如果不是第一個子命令,則將管道的輸入重定向到上一個子命令的輸出if (i > 0){if (dup2(pipefds[2 * (i - 1)], STDIN_FILENO) < 0){perror("無法重定向輸入");exit(1);}}// 如果不是最后一個子命令,則將管道的輸出重定向到下一個子命令的輸入if (i < pipeCount - 1){if (dup2(pipefds[2 * i + 1], STDOUT_FILENO) < 0){perror("無法重定向輸出");exit(1);}}// 關閉所有管道文件描述符for (int j = 0; j < 2 * pipeCount; j++){close(pipefds[j]);}// 執行子命令executeExternalCommand(pipes[i]);exit(0);}}// 關閉所有管道文件描述符for (int i = 0; i < 2 * pipeCount; i++){close(pipefds[i]);}// 等待所有子進程結束for (int i = 0; i < pipeCount; i++){int status;wait(&status);}
}int main()
{getcwd(current_directory, sizeof(current_directory)); // 獲取當前工作目錄while (1){// 顯示命令行提示符print_prompt();//讀取命令read_line();// 處理命令parse_cmd();}return 0;
}

運行的結果大家自行試驗就行了,我就不展示了。

至此,我們的實驗大功告成!如果大家有什么想法,可以在評論區提出,一起交流。

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

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

相關文章

Excel透視表:快速計算數據分析指標的利器

文章目錄 概述1.數據透視表基本操作1.1準備數據&#xff1a;1.2創建透視表&#xff1a;1.3設置透視表字段&#xff1a;1.4多級分類匯總和交叉匯總的差別1.5計算匯總數據&#xff1a;1.6透視表美化&#xff1a;1.7篩選和排序&#xff1a;1.8更新透視表&#xff1a; 2.數據透視-數…

【B站 heima】小兔鮮Vue3 項目學習筆記Day02

文章目錄 Pinia1.使用2. pinia-計數器案例3. getters實現4. 異步action5. storeToRefsx 數據解構保持響應式6. pinia 調試 項目起步1.項目初始化和git管理2. 使用ElementPlus3. ElementPlus 主題色定制4. axios 基礎配置5. 路由設計6. 靜態資源初始化和 Error lens安裝7.scss自…

Github 2024-05-24 開源項目日報 Top10

根據Github Trendings的統計,今日(2024-05-24統計)共有10個項目上榜。根據開發語言中項目的數量,匯總情況如下: 開發語言項目數量Python項目3非開發語言項目2TypeScript項目2JavaScript項目1Kotlin項目1C#項目1C++項目1Shell項目1Microsoft PowerToys: 最大化Windows系統生產…

軟件設計師備考筆記(十):網絡與信息安全基礎知識

文章目錄 一、網絡概述二、網絡互連硬件&#xff08;一&#xff09;網絡的設備&#xff08;二&#xff09;網絡的傳輸介質&#xff08;三&#xff09;組建網絡 三、網絡協議與標準&#xff08;一&#xff09;網絡的標準與協議&#xff08;二&#xff09;TCP/IP協議簇 四、Inter…

某神,云手機啟動?

某神自從上線之后&#xff0c;熱度不減&#xff0c;以其豐富的內容和獨特的魅力吸引著眾多玩家&#xff1b; 但是隨著劇情無法跳過&#xff0c;長草期過長等原因&#xff0c;近年脫坑的玩家多之又多&#xff0c;之前米家推出了一款云某神的app&#xff0c;目標是為了減少用戶手…

RedisTemplateAPI:String

文章目錄 ?1 String 介紹?2 命令?3 對應 RedisTemplate API???? 3.1 添加緩存???? 3.2 設置過期時間(單獨設置)???? 3.3 獲取緩存值???? 3.4 刪除key???? 3.5 順序遞增???? 3.6 順序遞減 ?4 以下是一些常用的API?5 應用場景 ?1 String 介紹 Str…

ue引擎游戲開發筆記(47)——設置狀態機解決跳躍問題

1.問題分析&#xff1a; 目前當角色起跳時&#xff0c;只是簡單的上下移動&#xff0c;空中仍然保持行走動作&#xff0c;并沒有設置跳躍動作&#xff0c;因此&#xff0c;給角色設置新的跳躍動作&#xff0c;并優化新的動作動畫。 2.操作實現&#xff1a; 1.實現跳躍不復雜&…

LabVIEW常用的電機控制算法有哪些?

LabVIEW常用的電機控制算法主要包括以下幾種&#xff1a; 1. PID控制&#xff08;比例-積分-微分控制&#xff09; 描述&#xff1a;PID控制是一種經典的控制算法&#xff0c;通過調節比例、積分和微分三個參數來控制電機速度和位置。應用&#xff1a;廣泛應用于直流電機、步…

Java中的繼承和多態

繼承 在現實世界中&#xff0c;狗和貓都是動物&#xff0c;這是因為他們都有動物的一些共有的特征。 在Java中&#xff0c;可以通過繼承的方式來讓對象擁有相同的屬性&#xff0c;并且可以簡化很多代碼 例如&#xff1a;動物都有的特征&#xff0c;有名字&#xff0c;有年齡…

Mybatis源碼剖析---第一講

Mybatis源碼剖析 基礎環境搭建 JDK8 Maven3.6.3&#xff08;別的版本也可以…&#xff09; MySQL 8.0.28 --> MySQL 8 Mybatis 3.4.6 準備jar&#xff0c;準備數據庫數據 把依賴導入pom.xml中 <properties><project.build.sourceEncoding>UTF-8</p…

Linux學習筆記:線程

Linux中的線程 什么是線程線程的使用原生線程庫創建線程線程的id線程退出等待線程join分離線程取消一個線程線程的局部存儲在c程序中使用線程使用c自己封裝一個簡易的線程庫 線程互斥(多線程)導致共享數據出錯的原因互斥鎖關鍵函數pthread_mutex_t :創建一個鎖pthread_mutex_in…

雷電預警監控系統:守護安全的重要防線

TH-LD1在自然界中&#xff0c;雷電是一種常見而強大的自然現象。它既有震撼人心的壯觀景象&#xff0c;又潛藏著巨大的安全風險。為了有效應對雷電帶來的威脅&#xff0c;雷電預警監控系統應運而生&#xff0c;成為現代社會中不可或缺的安全防護工具。 雷電預警監控系統的基本…

makefile 編寫規則

1.概念 1.1 什么是makefile Makefile 是一種文本文件&#xff0c;用于描述軟件項目的構建規則和依賴關系&#xff0c;通常用于自動化軟件構建過程。它包含了一系列規則和指令&#xff0c;告訴構建系統如何編譯和鏈接源代碼文件以生成最終的可執行文件、庫文件或者其他目標文件…

Node.js知識點以及案例總結

思考&#xff1a;為什么JavaScript可以在瀏覽器中被執行 每個瀏覽器都有JS解析引擎&#xff0c;不同的瀏覽器使用不同的JavaScript解析引擎&#xff0c;待執行的js代碼會在js解析引擎下執行 為什么JavaScript可以操作DOM和BOM 每個瀏覽器都內置了DOM、BOM這樣的API函數&#xf…

開源模型應用落地-食用指南-以最小成本博最大收獲

一、背景 時間飛逝&#xff0c;我首次撰寫的“開源大語言模型-實際應用落地”專欄已經完成了一半以上的內容。由衷感謝各位朋友的支持,希望這些內容能給正在學習的朋友們帶來一些幫助。 在這里&#xff0c;我想分享一下創作這個專欄的初心以及如何有效的&#xff0c;循序漸進的…

STM32F103C8T6 HC-SR04超聲波模塊——超聲波障礙物測距(HAl庫)

超聲波障礙物測距 一、HC-SR04超聲波模塊&#xff08;一&#xff09;什么是HC-SR04&#xff1f;&#xff08;二&#xff09;HC-SR04工作原理&#xff08;三&#xff09;如何使用HC-SR04&#xff08;四&#xff09;注意事項 二、程序編寫&#xff08;一&#xff09;CubeMX配置1.…

2024全新Langchain大模型AI應用與多智能體實戰開發

2024全新Langchain大模型AI應用與多智能體實戰開發 LangChain 就是一個 LLM 編程框架&#xff0c;你想開發一個基于 LLM 應用&#xff0c;需要什么組件它都有&#xff0c;直接使用就行&#xff1b;甚至針對常規的應用流程&#xff0c;它利用鏈(LangChain中Chain的由來)這個概念…

Facebook之魅:數字社交的體驗

在當今數字化時代&#xff0c;Facebook作為全球最大的社交平臺之一&#xff0c;承載著數十億用戶的社交需求和期待。它不僅僅是一個簡單的網站或應用程序&#xff0c;更是一個將世界各地的人們連接在一起的社交網絡&#xff0c;為用戶提供了豐富多彩、無與倫比的數字社交體驗。…

C++實現基礎二叉搜索樹(并不是AVL和紅黑樹)

本次實現的二叉搜索樹并不是AVL數和紅黑樹&#xff0c;只是了解流程和細節。 目錄 二叉搜索樹的概念K模型二叉搜索樹的實現二叉搜索樹的架構insert插入find 查找中序遍歷Inorder刪除earse替換法的思路情況一 &#xff1a;假如要刪除節點左邊是空的。在左邊時在右邊時 情況二&a…

文心智能體,零代碼構建情感表達大師智能體

前言 隨著智能體技術的突飛猛進&#xff0c;各行各業正迎來前所未有的變革與機遇。智能體&#xff0c;作為人工智能領域的重要分支&#xff0c;以其自主性、智能性和適應性&#xff0c;正逐步滲透到我們生活的每一個角落&#xff0c;成為推動社會進步和科技發展的新動力。 為了…