Linux--實現一個簡易的Shell(可以獲取命令提示符,支援管道)
Shell的實現過程
1.從標準輸入獲取命令列;
2.對使用者輸入的命令列進行解析,解析出要執行的指令和引數;
3.建立一個子程序;
4.子程序進行程式替換,父程序等待;
5.當子程序執行完畢,父程序從wait中返回,繼續下一次迴圈。
shell命令提示符
(1)一般的shell輸入命令之前都會有命令提示符:
[root@localhost 程序替換]#
root為使用者名稱,localhost為主機名,後面跟著的是當前目錄的相對路徑;
(2)所以在自己的Shell裡面也可以輸出這個命令提示符:
①獲取使用者名稱
getpwuid根據傳入的使用者ID,返回指向passwd的結構體,該結構體初始化了裡面的所有成員.使用者的ID通過getuid( )函式獲得。
//passwd結構體
struct passwd
{
char *pw_name; /* 使用者名稱*/
char *pw_passwd; /* 密碼.*/
__uid_t pw_uid; /* 使用者ID.*/
__gid_t pw_gid; /*組ID.*/
char *pw_gecos; /*真實名*/
char *pw_dir; /* 主目錄.*/
char *pw_shell; /*使用的shell*/
};
用於獲取使用者名稱:
struct passwd *pwd ; //定義一個passwd型別的指標pwd
pwd = getpwuid(getuid());
pwd->pw_name;
②獲取主機名:(通過gethostname函式)
//獲得主機名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname)); //hostname裡面存放的就是主機名
③獲取相對路徑
getcwd( )函式用於獲取當前目錄的絕對路徑,但是shell裡命令提示符的後面是相對路徑,路徑使用/進行分隔,所以最後一個/後面的內容為相對路徑。
//char * strtok ( char * str, const char * delimiters );
Parse函式用於路徑的切分,將路徑按照”/”進行切分,將切分後的結果儲存在字串陣列output裡面。
void Parse(char* input, char* output[])//用於將路徑進行切分,最後一個/後面為當前路徑
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
main函式中獲取相對路徑的有關程式碼:
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)
char* path2[100] = {0};
Parse(path,path2); //Parse函式用於切分路徑,將路徑按照"/"進行切分
char* path3 = NULL; //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
從標準輸入獲取命令
定義一個buf陣列用於存放輸入的指令(指令是字串的形式);
在main函式中編寫如下程式碼,用於獲取命令列:
char buf[1024] = {0};
fgets(buf);
對使用者輸入的命令列進行解析
通過ParseArg函式對指令按照空格進行切分,將切分出來的單獨的字串放入output數組裡面。(切分用到字串處理函式strtok)
void ParseArg(char* input, char* output[]) //用於對輸入的命令進行切分
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
在main函式中編寫如下程式碼:
//解析字串,解析出指令和引數
char* argv[100] = {};
ParseArg(buf,argv); //ParseArg函式用於對命令列進行切分,buf存放從標準輸入獲取的指令,argv存放解析出來的命令引數
建立一個子程序,子程序進行程式替換,父程序等待
用Exec函式進行封裝,Exec函式裡面建立一個子程序,子程序進行程式替換,父程序進行等待(父程序不關心子程序的退出狀態,所以可以採用wait(NULL))
void Exec(char* argv[]) //建立子程序執行程序程式替換
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child
execvp(argv[0],argv);
//execvp失敗會執行下面的語句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
Shell簡易版
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
void Parse(char* input, char* output[])//用於將路徑進行切分,最後一個/後面為當前路徑
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
void ParseArg(char* input, char* output[]) //用於對輸入的命令進行切分
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
void Exec(char* argv[]) //建立子程序執行程序程式替換
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
execvp(argv[0],argv);
//execvp失敗會執行下面的語句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
int main()
{
while(1)
{
//首先顯示shell提示
//通過getuid獲得使用者的id,然後通過getpwuid通過使用者的id查詢使用者的passwd資料來獲取系統登入的使用者名稱
//獲得當前路徑
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)
char* path2[100] = {0};
Parse(path,path2);
char* path3 = NULL; //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
struct passwd *pwd;
pwd = getpwuid(getuid());
//獲得主機名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname));
printf("[%[email protected]%s %s MyShell]",pwd->pw_name,hostname,path3);
fflush(stdout);
//從標準輸入讀取字串
char buf[1024] = {0};
gets(buf);
//解析字串,解析出指令和引數
char* argv[100] = {};
ParseArg(buf,argv);
//子程序執行exec函式,將這個過程封裝在Exec函式裡
Exec(argv);
}
return 0;
}
執行結果:(注意到自己編寫的shell的命令提示符後面會帶上MyShell,這是為了與自己的shell進行區分)
讓Shell支援管道
管道的實現方法
1.管道”|”是將”|”左邊指令的執行結果作為”|”右邊的輸入。
2.我們可以將一條帶有”|”的指令進行分割,將管道左邊指令的輸出結果重定向到管道的輸入(也就是管道的寫端),將標準輸入重定向至管道的讀端。讓兩個程序分別執行兩邊重定向後的指令。
管道的具體實現
(1)首先判斷一條指令是否包含管道(指令已經存放至char*的數組裡面,且該陣列以NULL作為結束標誌)
int IsPipe(char* argv[])
{
int i = 0;
while(argv[i] != NULL)
{
if(strcmp(argv[i], "|") == 0)
{
return i+1; //i的位置是管道,則i+1就是管道的下一個命令
}
++i;
}
return 0;
}
(2)將管道左右兩邊的指令進行切分,切分後的分別放入兩個char*數組裡,並保證每個陣列以NULL作為結束標誌
void ParsePipe(char* input[], char* output1[],char* output2[])//用於將input按照|進行切分,最後一個後面為當前路徑
{
int i = 0;
int size1 = 0;
int size2 = 0;
int ret = IsPipe(input); //ret是input陣列中管道"|"的下一個位置
while(strcmp(input[i], "|")!=0)
{
output1[size1++] = input[i++];
}
output1[size1] = NULL; //將分割出來的兩個char*陣列都以NULL結尾
int j = ret;//j指向管道的後面那個字元
while(input[j] != NULL)
{
output2[size2++] = input[j++];
}
output2[size2] = NULL;
}
(3)執行包含管道的指令(利用execvp函式,首先建立一個程序,讓子程序建立一個管道,再創讓子程序建一個子程序(孫子程序),讓孫子程序執行管道左邊的指令,並將輸出結果重定向入管道,讓子程序從執行管道右邊的指令)
void ExecvPipe(char* argv1[],char* argv2[]) //argv1為左邊指令及引數的陣列argv2則為存放管道右邊指令及引數的陣列
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child,建立一個管道
int fd[2];
int ret = pipe(fd);
if(fork() == 0 )
{
//子程序建立一個子程序,用於執行管道之前的命令
close(fd[0]); //孫子程序關閉掉讀端
dup2(fd[1],1); //將標準輸出作為管道的標準輸入
execvp(argv1[0],argv1);
}
wait(NULL);
close(fd[1]); //子程序關閉掉寫端
dup2(fd[0],0); //將標準輸入重定向至管道的讀端
execvp(argv2[0],argv2);
//execvp失敗會執行下面的語句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
(4)整體程式碼
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
//用於將路徑進行切分,最後一個/後面為當前路徑
void Parse(char* input, char* output[])
{
char* p = strtok(input,"/");
int i = 0;
while(p!= NULL)
{
output[i++] = p;
p=strtok(NULL,"/");
}
}
//用於對輸入的命令進行切分
void ParseArg(char* input, char* output[])
{
char* p = strtok(input," ");
int output_size = 0;
while(p != NULL)
{
output[output_size++] = p;
p = strtok(NULL," ");
}
output[output_size] = NULL;
}
void Exec(char* argv[]) //建立子程序執行程序程式替換
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child
execvp(argv[0],argv);
//execvp失敗會執行下面的語句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
//判斷指令是否包含管道
int IsPipe(char* argv[])
{
int i = 0;
while(argv[i] != NULL)
{
if(strcmp(argv[i], "|") == 0)
{
return i+1; //i是管道,則i+1就是管道的下一個命令
}
++i;
}
return 0;
}
void ParsePipe(char* input[], char* output1[],char* output2[])//用於將input按照|進行切分,最後一個後面為當前路徑
{
int i = 0;
int size1 = 0;
int size2 = 0;
int ret = IsPipe(input);
while(strcmp(input[i], "|")!=0)
{
output1[size1++] = input[i++];
}
output1[size1] = NULL;
int j = ret;//j指向管道的後面那個字元
while(input[j] != NULL)
{
output2[size2++] = input[j++];
}
output2[size2] = NULL;
}
void ExecvPipe(char* argv1[],char* argv2[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork()");
}
else if(pid == 0)
{
//child,建立一個管道
int fd[2];
int ret = pipe(fd);
if(fork() == 0 )
{
//子程序建立一個子程序,用於執行管道之前的命令
close(fd[0]); //孫子程序關閉掉讀端
dup2(fd[1],1); //將標準輸出作為管道的標準輸入
execvp(argv1[0],argv1);
}
wait(NULL);
close(fd[1]); //關閉掉寫端
dup2(fd[0],0);
execvp(argv2[0],argv2);
//execvp失敗會執行下面的語句
perror("execvp()");
exit(0);
}
else
{
//parent
wait(NULL);
}
}
int main()
{
//獲得當前路徑
while(1)
{
//首先顯示shell提示
//通過getuid獲得使用者的id,然後通過getpwuid通過使用者的id查詢使用者的passwd資料來獲取系統登入的使用者名稱
//獲得當前路徑
char path[100] = {0};
getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)
char* path2[100] = {0};
Parse(path,path2);
char* path3 = NULL; //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑
int i = 0;
while(path2[i] !=NULL)
{
path3= path2[i];
i++;
}
struct passwd *pwd;
pwd = getpwuid(getuid());
//獲得主機名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname));
printf("[%[email protected]%s %s MyShell]",pwd->pw_name,hostname,path3);
fflush(stdout);
//從標準輸入讀取字串
char buf[1024] = {0};
//解析字串,解析出指令和引數
char* argv[100];
ParseArg(buf,argv);
//判斷解析出來的字串數組裡面是否含有管道
int ret1 = IsPipe(argv);
if(ret1 > 0)
{
//是管道
char* argv1[10];
char* argv2[10];
ParsePipe(argv,argv1,argv2);
ExecvPipe(argv1,argv2);
}
else
{
Exec(argv);
}
}
return 0;
相關推薦
Linux--實現一個簡易的Shell(可以獲取命令提示符,支援管道)
Shell的實現過程 1.從標準輸入獲取命令列; 2.對使用者輸入的命令列進行解析,解析出要執行的指令和引數; 3.建立一個子程序; 4.子程序進行程式替換,父程序等待; 5.當子程序執行完畢,父程序從wait中返回,繼續下一次迴圈。 shell
實現一個簡單shell(支援重定向)
5.16更新:支援重定向 我們知道對於Linux,shell就是個命令列直譯器,當我們輸入相關的命令,會去執行相關的操作。 比如當我們輸入ls -a -l命令,shell就會打印出當前目錄的內容,這是如何實現的?shell自己就是一個程序,當我們輸入ls之類
18、OpenCV Python 簡單實現一個圖片生成(類似抖音生成字母人像)
gaussian int read 。。 str gray clas range TE 1 __author__ = "WSX" 2 import cv2 as cv 3 import numpy as np 4 5 def local_threshold(i
linux下nginx編譯安裝(抄別人的,方便檢視)
原路徑:https://blog.csdn.net/youcijibi/article/details/75050993 正式開始前,編譯環境gcc g++ 開發庫之類的需要提前裝好。 如果是ububtu平臺初始安裝編譯安裝則使用如下指令: apt-get install build-e
巧用margin/padding的百分比值實現高度自適應(多用於佔位,避免閃爍)
一個基礎卻又容易混淆的css知識點 本文依賴於一個基礎卻又容易混淆的css知識點:當margin/padding取形式為百分比的值時,無論是left/right,還是top/bottom,都是以父元素的width為參照物的! 也許你會說,left/right以父元素的w
popopWindow 實現頂部篩選選單(頂部不變,底部陰暗)思路記錄
先上效果:需要解決的問題:1.popopWindow 位置(此處為toolbar正下方)2.頂部不變,底部陰暗3.popopWindow 根據item個數適配高度,並設定最大height一、popopWindow 位置首先第一點,傳入的parent為toolbar。下面的lo
每天學習一個LINUX命令:passwd 修改設置用戶密碼 (pass word 口令,通過語)
pass 更新 vpd 技術分享 oot 修改 時間 -o RoCE passwd命令用於設置用戶的認證信息,包括用戶密碼、密碼過期時間等。系統管理者則能用它管理系統用戶的密碼。只有root可以指定用戶名稱,一般用戶只能變更自己的密碼。 語法: passwd [參數] [
Linux下的簡易shell實現
Linux系統的shell作為作業系統的外殼,為使用者提供使用作業系統的介面。 它是命令語言、命令解釋程式及程式設計語言的統稱。 相當於bash的一個子程序,父程序等待,子程序進行程式替換。 shell充當一個橋樑:將使用者的命令翻譯給核心(kernel)處理;同時,將核心的
實現一個簡易的vue的mvvm(defineProperty)
這是一個最近一年很火的面試題,很多人看到這個題目從下手,其實查閱一些資料後,簡單的模擬還是不太難的: vue不相容IE8以下是因為他的實現原理使用了 Object.defineProperty 的get和set方法,首先簡單介紹以下這個方法 我們看到控制檯
Linux下模擬實現一個微型shell
首先我們先看一下shell的執行過程: shell從使用者讀入字串"ls",並建立一個新的程序,在那個程序中執行ls程式並等待那個程序結束。然後shell讀取新的一行輸入,建立一個新的程序,在這個程序中執行程式並等待這個程序結束。 所以我
【Android進階】如何寫一個很屌的動畫(1)---先實現一個簡易的自定義動畫框架
class MyView extends View { public void onDraw(Canvas canvas) { super.onDraw(canvas); invalidate(); } } 這樣一來,View每次繪製都是觸發下一次繪製,不過
Linux-C基礎知識學習:C語言作業-將5個學生成績儲存在一個數組中,單獨實現一個計算平均成績的average函式, 在main函式中獲取該函式返回的平均值,並列印。
Linux基礎知識學習 C語言作業:將5個學生成績儲存在一個數組中,單獨實現一個計算平均成績的average函式, 在main函式中獲取該函式返回的平均值,並列印。 #include <s
用Python實現一個簡易的“聽歌識曲”demo(一)
0. 背景 最近兩年,“聽歌識曲”這個應用在國內眾多的音樂類APP火熱上線,比如網易雲音樂,QQ音樂。使用者可以通過這個功能識別當前環境里正在播放的歌曲名字,聽起來很酷。其實“聽歌識曲”這個想法最早是由一家叫Shazam的國外公司提出的。 - 20
linux Shell程式設計--獲取命令執行返回結果
1.將命令執行的整型數字結果返回並賦值給變數。 使用反引號包裝命令字串 TAB上的` scarlett@scarlett-X550VXK:~$ line=`cat /etc/passwd| wc -
Androidstudio實現一個簡易的加法器——分享兩種方法實現(日常作業練習)
Androidstudio實現一個簡易的加法器——分享兩種方法實現(日常作業練習) &
用java實現一個簡易編譯器1-詞法解析入門
new 概念 自加 我們 sta 數字 獲得 () 操作系統 本文對應代碼下載地址為: http://download.csdn.net/detail/tyler_download/9435103 視頻地址: http://v.youku.com/v_show/id_XMT
Linux基礎管理——文本處理(小命令組合解決大問題)
文本處理命令 grep 正則表達式 擴展正則表達式 前言: 在服務器中對日誌進行處理是十分常見的工作,所以運維對於各種文本工具來查看、分析、統計,是必備的基本工。那麽學習正則表達式、grep、egrep、和tr、sort、uniq等常見的文件處理命令就十分有必要了。1、工具分類 文
使用 RxJS 實現一個簡易的仿 Elm 架構應用
inter bject compute 幫助 規律 returns date rgs 個人愛好 使用 RxJS 實現一個簡易的仿 Elm 架構應用
在Android中實現一個簡易的Http服務器
.get json data ESS public 瀏覽器 顯示 getmethod blank 最近遇到一個需求需要在App中創建一個Http服務器供供瀏覽器調用,用了下開源的微型Htpp服務器框架:NanoHttpd,項目地址:https://github.com/Na
實現一個簡易的express中間件
str 修改 clas middle 分享圖片 next 測試 inf 返回 代碼: // 通過閉包實現單例 const Middlewave = (function(){ let instance; class Middlewave{ c