亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

編寫自己的Shell解釋器

系統(tǒng) 2098 0

摘要:本期的目的是向大家介紹shell的概念和基本原理,并且在此基礎(chǔ)上動(dòng)手做一個(gè)簡(jiǎn)單shell解釋器。同時(shí),還將就用到的一些 linux環(huán)境編程的知識(shí)做一定講解。

本文適合的讀者對(duì)象

?????? 對(duì)linux環(huán)境上的c語(yǔ)言開發(fā)有一定經(jīng)驗(yàn);

對(duì)linux環(huán)境編程(比如進(jìn)程、管道)有一點(diǎn)了解。
概述

本章的目的是帶大家了解shell的基本原理,并且自己動(dòng)手做一個(gè)shell解釋器。為此,

首先,我們解釋什么是shell解釋器。

其次,我們要大致了解shell解釋器具有哪些功能;

最后,我們具體講解如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 shell 解釋器,并對(duì)需要用到一些 linux環(huán)境編程的知識(shí)做一定講解,并提醒你如果想深入掌握,應(yīng)該去看哪些資料。

Shell解釋器是什么?

Shell解釋器是一個(gè)程序。對(duì),是一個(gè)程序,而且,它就在我們的身邊。在linux系統(tǒng)中,當(dāng)我們輸入用戶名和密碼登陸之后,我們就開始執(zhí)行一個(gè)shell解釋器程序,通常是 /bin/bash,當(dāng)然也可以是別的,比如/bin/sh。(詳細(xì)概念請(qǐng)看第一期中的shell有關(guān)部分)

提示:在 /etc/passwd 文件中,每個(gè)用戶對(duì)應(yīng)的最后一項(xiàng),就指定了該用戶登陸之后,要執(zhí)行的shell解釋器程序。

在 linux 字符界面下,輸入

man bash

調(diào)出 bash 的幫助頁(yè)面

幫助的最開始就對(duì)bash下了一個(gè)定義:

bash 是一個(gè)兼容于 sh 的命令語(yǔ)言解釋器,它從標(biāo)準(zhǔn)輸入或者文件中讀取命令并執(zhí)行。它的意圖是實(shí)現(xiàn) IEEE POSIX標(biāo)準(zhǔn)中對(duì) shell和工具所規(guī)范的內(nèi)容。

Shell解釋器的作用

在登陸 linux 系統(tǒng)之后,屏幕上就會(huì)出現(xiàn)一行提示符,在我的機(jī)器上,是這樣的:

?????? [root@stevens root]#

這行提示符就是由bash解釋器打印出來(lái)的,這說(shuō)明,現(xiàn)在已經(jīng)處于 bash 的控制之下了,也同時(shí)提示用戶,可以輸入命令。用戶輸入命令,并回車確認(rèn)后,bash分析用戶的命令,如果用戶的命令格式正確,那么bash就按照用戶的意思去做一些事情。

比如,用戶輸入:

[root@stevens root]#? echo “hello, world”

那么,bash就負(fù)責(zé)在屏幕上打印一行“hello world”。

如果,用戶輸入:

[root@stevens root]#? cd /tmp

那么,bash就把用戶的當(dāng)前目錄改變?yōu)?/tmp。

所以,shell解釋器的作用就是對(duì)用戶輸入的命令進(jìn)行“解釋”,有了它,用戶才可以在 linux 系統(tǒng)中任意揮灑。沒(méi)有它的幫助,你縱然十八般本領(lǐng)在身,也施展不出。

bash每次在“解釋”完用戶命令之后,又打印出一行提示符,然后繼續(xù)等待用戶的下一個(gè)命令。這種循環(huán)式的設(shè)計(jì),使得用戶可以始終處于 bash 的控制之下。除非你輸入 exit、logout明確表示要退出 bash。

Shell語(yǔ)法梗概

我們不停的命令 bash 做這做那,一般情況下它都很聽話,按你的吩咐去做??捎袝r(shí)候,它會(huì)對(duì)你說(shuō):“嗨,老兄,你的命令我理解不了,無(wú)法執(zhí)行”。例如,你輸入這樣的命令:

[root@stevesn root]# aaaaaa

bash會(huì)告訴你:

bash: aaaaaa: command not found

是的,你必須說(shuō)的讓它能聽懂,否則它就給你這么一句抱怨,當(dāng)然也還會(huì)有其它的牢騷。

那么,什么樣格式的命令,它才能正確理解執(zhí)行了?這就要引出shell 的語(yǔ)言規(guī)范了。

Shell作為一個(gè)命令語(yǔ)言解釋器,有一套自己的語(yǔ)言規(guī)范,凡是符合這個(gè)規(guī)范的命令,它就可以正確執(zhí)行,否則就會(huì)報(bào)錯(cuò)。這個(gè)語(yǔ)言規(guī)范是在 IEEE POSIX的第二部分:“shell和tools規(guī)范”中定義的。關(guān)于這份規(guī)范,可以在這里看到。

官方的東西,總是冗長(zhǎng)而且晦澀,因?yàn)樗龅矫婷婢愕角也荒苡衅凭`。如果讀者有興趣,可以仔細(xì)研究這份規(guī)范。而我們的目的只是理解shell的實(shí)現(xiàn)思想,然后去實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 shell 解釋器,所以沒(méi)必要陷入枯燥的概念之中。

現(xiàn)在請(qǐng)繼續(xù)在 linux 字符界面下輸入 man bash,調(diào)出 bash 的幫助頁(yè)面,然后找到 “shell語(yǔ)法”那一部分,我們就是以這里的描述作為實(shí)現(xiàn)的依據(jù)。

在 bash幫助的“shell 語(yǔ)法”一節(jié),是這樣來(lái)定義shell 語(yǔ)法的:

l???????? 簡(jiǎn)單命令

簡(jiǎn)單命令是(可選的)一系列變量賦值, 緊接著是空白字符分隔的詞和重定向符號(hào), 最后以一個(gè)控制操作符結(jié)束. 第一個(gè)詞指明了要執(zhí)行的命令, 它被作為第 0 個(gè)參數(shù). 其余詞被作為這個(gè)命令的參數(shù).

?????? 這個(gè)定義可以這樣來(lái)理解:

1、? 可以有變量賦值,例如

a=10 b=20 export a b

2、? “詞”是以空白字符分隔開的,空白字符包括制表符(tab)和空格,例如:

ls /tmp

就是兩個(gè)詞,一個(gè) ls,一個(gè) /tmp

3、可以出現(xiàn)重定向符號(hào),重定向符號(hào)是“>”和“<”,例如:

echo “hello world” > /tmp/log

4、? 簡(jiǎn)單命令結(jié)束于控制操作符,控制操作符包括:

||? &?? &&???? ;?? ;;? ( )?? |? <newline>

例如,用戶輸入:

ls /tmp

用戶最后敲的回車鍵就是控制操作符 newline,表示要結(jié)束這個(gè)簡(jiǎn)單命令。

如果用戶輸入:

echo “100” ; echo “200”

那么這是兩個(gè)簡(jiǎn)單命令,第一個(gè)結(jié)束于“;”,第二個(gè)結(jié)束于newline。

5、? 簡(jiǎn)單命令的第一個(gè)詞是要執(zhí)行的命令,其余的詞都是這個(gè)命令的參數(shù),例如:

echo “hello world” echo

第一個(gè)echo 是命令,第二個(gè)詞“hello world”是參數(shù)1,第三個(gè)詞 echo 是參數(shù)2,而不再作為一個(gè)命令了。

簡(jiǎn)單命令是 shell 語(yǔ)法中最小的命令,通過(guò)簡(jiǎn)單命令的組合,又可以得到管道命令和列表命令。

l???????? 管道(命令)

管道是一個(gè)或多個(gè)簡(jiǎn)單命令的序列,兩個(gè)簡(jiǎn)單命令之間通過(guò)管道符號(hào)(“|”)來(lái)分隔

例如

echo “hello world” | wc –l

就是一個(gè)管道,它由兩個(gè)簡(jiǎn)單命令組成,兩個(gè)簡(jiǎn)單命令之間用管道符號(hào)分隔開。

我們可以看到,管道符號(hào)“|”也是屬于上面提到的控制操作符。

根據(jù)這個(gè)定義,一個(gè)簡(jiǎn)單命令也同時(shí)是一個(gè)管道。

管道的作用是把它前面的那個(gè)簡(jiǎn)單命令的輸出作為后面那個(gè)簡(jiǎn)單命令的輸入,就上面這個(gè)例子來(lái)說(shuō):

echo “hello world” 本來(lái)是要在標(biāo)準(zhǔn)輸出(屏幕)上打印 “hello world” 的,但是管道現(xiàn)在不讓結(jié)果輸出到屏幕上,而是“流”到 wc –l 這個(gè)簡(jiǎn)單命令,wc –l 就把“流”過(guò)來(lái)的數(shù)據(jù)作為它的標(biāo)準(zhǔn)輸入進(jìn)行計(jì)算,從而統(tǒng)計(jì)出結(jié)果是 1 行。

關(guān)于管道更詳細(xì)的內(nèi)容,我們?cè)诤竺婢唧w實(shí)現(xiàn)管道的時(shí)候再說(shuō)明。

l???????? 列表(命令):

列表是一個(gè)或多個(gè)管道組成的序列,兩個(gè)管道之間用操作符 ;, &, &&, 或 || 分隔。我們看到,這幾個(gè)操作符都屬于控制操作符。

例如

echo “hello world” | wc –l ; echo “nice to meet you”

就是一個(gè)列表,它由兩個(gè)管道組成,管道之間用分號(hào)(;)隔開

分號(hào)這種控制操作符僅僅表示一種執(zhí)行上的先后順序。

l???????? 復(fù)合命令

?????? 這個(gè)定義比較復(fù)雜,實(shí)現(xiàn)起來(lái)也有相當(dāng)難度,在咱們這個(gè)示例程序中,就不實(shí)現(xiàn)了。

以上是 shell 語(yǔ)法規(guī)范的定義,我們的 shell 程序就是要以此規(guī)范為依據(jù),實(shí)現(xiàn)對(duì)簡(jiǎn)單命令、管道和列表的解釋。對(duì)于列表中的控制操作符,我們只支持分號(hào)(;),其它的留給讀者自己來(lái)實(shí)現(xiàn)。

?????? 接下來(lái),我們具體介紹如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 shell解釋器。
實(shí)現(xiàn)shell實(shí)例
程序主框架

?????? 主程序很簡(jiǎn)單,它在做一些必要的初始化工作之后,進(jìn)入這樣一個(gè)循環(huán):

u?????? 打印提示符并等待用戶輸入

u?????? 獲取用戶輸入

u?????? 分析用戶輸入

u?????? 解釋執(zhí)行;

如果用戶輸入 logout或者 exit 之后,才退出這個(gè)循環(huán)。

用類似偽代碼的形式表示如下:

while(1) {

?????? print_prompt();

?????? get_input();

?????? parse_input();

?????? if(“l(fā)ogout” || “exit”)

????????????? break;

?????? do_cmd();

}

讀取用戶輸入

如何獲取用戶輸入?一種方法是通過(guò) getchar() 從標(biāo)準(zhǔn)輸入每次讀一個(gè)字符,如果讀到的字符是 ‘\n’,說(shuō)明用戶鍵入了回車鍵,那么就把此前讀到的字符串作為用戶輸入的命令。

代碼如下:

int len = 0;

int ch;

char buf[300];

ch = getchar();

while(len < BUFSIZ && ch != '\n') {

?????? buf[len++] = ch;

?????? ch = getchar();

}

if(len == BUFSIZ) {

?????? printf("command is too long\n");

?????? break;

}

buf[len] = '\n';

len++;

buf[len] = 0;

但是,我們注意到,在 bash 中,可以用“<-”和“->”鍵在命令行中左右移動(dòng),可以用上下鍵調(diào)用以前使用的命令,可以用退格鍵來(lái)刪除一個(gè)字符,還可以用 tab 鍵來(lái)進(jìn)行命令行補(bǔ)全。我們的shell如果也要支持這些功能,那么就必須對(duì)這些鍵進(jìn)行處理。這樣僅僅對(duì)用戶輸入的讀取就非常麻煩了。

實(shí)際上,任何需要一個(gè)獲取用戶輸入的程序,都會(huì)涉及到同樣的問(wèn)題,如何象bash 那樣處理鍵盤?GNU readline 庫(kù)就是專門解決這個(gè)問(wèn)題的,它把對(duì)鍵盤的操作完全封裝起來(lái),對(duì)外只提供一個(gè)簡(jiǎn)單的調(diào)用接口。有了它,對(duì)鍵盤的處理就不再讓人頭疼了。

關(guān)于 readline 庫(kù)的詳細(xì)信息,可以通過(guò) man readline 來(lái)看它的幫助頁(yè)面。在我們的 shell 程序中,我是這樣來(lái)使用 readline的。

char* line;

char prompt[200];

while(1) {

?????? set_prompt(prompt);

?????? if(!(line = readline(prompt)))

????????????? break;

?????? 。。。。。。

}

首先通過(guò) set_prompt() 來(lái)設(shè)置要輸出的提示符,然后以提示符作為參數(shù)調(diào)用 readline(),這個(gè)函數(shù)等待用戶輸入,并動(dòng)態(tài)創(chuàng)建一塊內(nèi)存來(lái)保存用戶輸入的數(shù)據(jù),可以通過(guò)返回的指針 line 得到這塊內(nèi)存。在每次處理完用戶輸入的命令之后,我們必須自己負(fù)責(zé)來(lái)釋放這塊內(nèi)存。

有了 readline 之后,我們就可以象 bash 那樣使用鍵盤了。

在通過(guò) readline 獲取用戶輸入之后,下一步就是對(duì)用戶輸入的命令進(jìn)行分析。

命令行分析

對(duì)命令行的分析,實(shí)際上是一個(gè)詞法分析過(guò)程。學(xué)過(guò)編譯原理的朋友,都聽說(shuō)過(guò) lex 和yacc 的大名,它們分別是詞法分析和語(yǔ)法分析工具。Lex 和 yacc 都有GNU的版本(open source 的思想實(shí)在是太偉大了,什么好東東都有免費(fèi)的用),分別是 flex 和 bison。

所謂“工欲善其事,必先利其器”,既然有這么好的工具,那我們就不必辛辛苦苦自己進(jìn)行詞法分析了。對(duì),我們要用 lex 來(lái)完成枯燥的命令行詞法分析工作。

“去買本《lex與yacc》(中國(guó)電力出版社)來(lái)看吧。第一次學(xué)當(dāng)然稍微有點(diǎn)難度,不過(guò)一旦掌握了,以后再碰到類似問(wèn)題,就可以多一個(gè)利器,可以節(jié)省勞動(dòng)力了。

在我們的這個(gè) shell 程序中,用 flex 來(lái)完成詞法分析工作。相對(duì)語(yǔ)法分析來(lái)說(shuō),詞法分析要簡(jiǎn)單的多。由于我們只是做一個(gè)簡(jiǎn)單的 shell,因此并沒(méi)有用到語(yǔ)法分析,而實(shí)際上在 bash 的實(shí)現(xiàn)代碼中,就用到了語(yǔ)法分析和 yacc。

關(guān)于 lex 的細(xì)節(jié),在這里我就不能多說(shuō)了。Lex程序,通常分為三個(gè)部分,其中進(jìn)行語(yǔ)法分析工作的就是它的第二部分: “規(guī)則”。規(guī)則定義了在詞法分析過(guò)程中,遇到什么樣的情況,應(yīng)該如何處理。

詞法分析的思路,就是根據(jù)前面定義的“shell語(yǔ)法規(guī)范”來(lái)把用戶輸入的命令行拆解成

首先,我們要把用戶輸入的命令,以空白字符(tab鍵或者空格)分隔成一個(gè)個(gè)的參數(shù),并把這些參數(shù)保存到一個(gè)參數(shù)數(shù)組中。但是,這其中有幾種特殊情況。

一、如果遇到的字符是“;”、“>”、“<”或“|”,由于這些符號(hào)是管道或者列表中所用到的分隔符,因此必須把它們當(dāng)作一個(gè)單獨(dú)的參數(shù)。

二、以雙引號(hào)(”)括起來(lái)的字符串要作為一個(gè)單獨(dú)的參數(shù),即使其中出現(xiàn)了空白字符、“;”、“>”、“<”、“|”。其實(shí),在POSIX標(biāo)準(zhǔn)中,對(duì)引號(hào)的處理相當(dāng)復(fù)雜,不僅包括雙引號(hào)(”),還有單引號(hào)(’)、反引號(hào)(`),在什么情況下,應(yīng)該用什么樣的引號(hào)以及對(duì)引號(hào)中的字符串應(yīng)該如何解釋,都有一大堆的條款。我們這里只是處理一種極簡(jiǎn)單的情況。

其次,如果我們遇到換行符(’\n’),那么就結(jié)束本次命令行分析。根據(jù)前面定義的 shell 語(yǔ)法規(guī)范,最上層的是列表命令,因此下一步是把所有的參數(shù)作為一個(gè)列表命令來(lái)處理。

根據(jù)這個(gè)思路,我們來(lái)看對(duì)應(yīng)的 lex 規(guī)則。

%%

"\""??????????? {BEGIN QUOTE;}

<QUOTE>[^\n"]+? {add_arg(yytext);}

<QUOTE>"\""???? {BEGIN 0;}

<QUOTE>\n?????? {BEGIN 0; do_list_cmd(); reset_args();}

";"???????????? {add_simple_arg(yytext);}

">"???????????? {add_simple_arg(yytext);}

"<"???????????? {add_simple_arg(yytext);}

"|"???????????? {add_simple_arg(yytext);}

[^ \t\n|<>;"]+? {add_arg(yytext);}

\n????????????? {do_list_cmd(); reset_args();}

.?????????????? ;

%%

我們對(duì)這些規(guī)則逐條解釋:

1-4這4條規(guī)則,目的是為了在命令行中支持引號(hào),它們用到了 lex 規(guī)則的狀態(tài)特性。

1、"\""??????????? {BEGIN QUOTE;}

2、<QUOTE>[^\n"]+? {add_arg(yytext);}

3、<QUOTE>"\""???? {BEGIN 0;}

4、<QUOTE>\n?????? {BEGIN 0; do_list_cmd(); reset_args();}

1、? 如果掃描到引號(hào)( “),那么進(jìn)入 QUOTE 狀態(tài)。在這個(gè)狀態(tài)下,即使掃描到空白字符或“;”、“>”、“<”、“|”,也要當(dāng)作普通的字符。

2、? 如果處于 QUOTE狀態(tài),掃描到除引號(hào)和回車以外的字符,那么調(diào)用 add_arg()函數(shù),把整個(gè)字符串加入到參數(shù)數(shù)組中。

3、? 如果處于QUOTE狀態(tài),掃描到引號(hào),那么表示匹配了前面的引號(hào),于是恢復(fù)到默認(rèn)狀態(tài)。

4、? 如果處于QUOTE狀態(tài),掃描到回車,那么結(jié)束了本次掃描,恢復(fù)到默認(rèn)狀態(tài),并執(zhí)行 do_list_cmd(),來(lái)執(zhí)行對(duì)列表命令的處理。

以下幾條規(guī)則,是在處于默認(rèn)狀態(tài)的情況下的處理。

5、";"????????????? {add_simple_arg(yytext);}

6、">"????????????? {add_simple_arg(yytext);}

7、"<"????????????? {add_simple_arg(yytext);}

8、"|"?????????????? {add_simple_arg(yytext);}

9、[^ \t\n|<>;"]+????? {add_arg(yytext);}

10、\n?????????????? {do_list_cmd(); reset_args();}

5、? 如果遇到分號(hào)(;),因?yàn)檫@是一個(gè)列表命令結(jié)束的操作符,所以作為一個(gè)單獨(dú)的參數(shù),執(zhí)行 add_simple_arg(),將它加入?yún)?shù)數(shù)組。

6、? 如果遇到 >,因?yàn)檫@是一個(gè)簡(jiǎn)單命令結(jié)束的操作符,所以作為一個(gè)單獨(dú)的參數(shù),執(zhí)行 add_simple_arg(),將它加入?yún)?shù)數(shù)組。

7、? 如果遇到 <,因?yàn)檫@是一個(gè)簡(jiǎn)單命令結(jié)束的操作符,所以作為一個(gè)單獨(dú)的參數(shù),執(zhí)行 add_simple_arg(),將它加入?yún)?shù)數(shù)組。

8、? 如果遇到管道符號(hào)(|),因?yàn)檫@是一個(gè)管道命令結(jié)束的操作符,所以作為一個(gè)單獨(dú)的參數(shù),執(zhí)行 add_simple_arg(),將它加入?yún)?shù)數(shù)組。

9、? 對(duì)于不是制表符(tab)、換行符(’\n’)、| 、<、>和分號(hào)(;)以外的字符序列,作為一個(gè)普通的參數(shù),加入?yún)?shù)數(shù)組。

10、????????????? 如果遇到換行符,那么結(jié)束本次掃描,執(zhí)行 do_list_cmd(),來(lái)執(zhí)行對(duì)列表命令的處理。

11、????????????? 對(duì)于任意其它字符,忽略

通過(guò) lex 的“規(guī)則”把用戶輸入的命令行分解成一個(gè)個(gè)的參數(shù)之后,都要執(zhí)行 do_list_cmd() 來(lái)執(zhí)行對(duì)列表命令的處理。

命令處理

首先是對(duì)處于“shell語(yǔ)法規(guī)范”中最上層的列表命令的處理。

l???????? 列表命令的處理過(guò)程:

依次檢查參數(shù)數(shù)組中的每一個(gè)參數(shù),如果是分號(hào)(;),那么就認(rèn)為分號(hào)前面的所有參數(shù)組成了一個(gè)管道命令,調(diào)用 do_pipe_cmd() 來(lái)執(zhí)行對(duì)管道命令的處理。如果掃描到最后,不再有分號(hào)出現(xiàn),那么把剩下的所有參數(shù)作為一個(gè)管道命令處理。

代碼很簡(jiǎn)單:

static void do_list_cmd()

{

?????? int i = 0;

?????? int j = 0;

?????? char* p;

?????? while(argbuf[i]) {

????????????? if(strcmp(argbuf[i], ";") == 0) {//? ;

???????????????????? p = argbuf[i];

???????????????????? argbuf[i] = 0;

???????????????????? do_pipe_cmd(i-j, argbuf+j);

???????????????????? argbuf[i] = p;

???????????????????? j = ++i;

????????????? } else

???????????????????? i++;

?????? }

?????? do_pipe_cmd(i-j, argbuf+j);

}

接下來(lái)是對(duì)管道命令的處理。
管道命令的處理

管道是進(jìn)程間通信(IPC)的一種形式,關(guān)于管道的詳細(xì)解釋在《unix高級(jí)環(huán)境編程》第14章:進(jìn)程間通信以及《unix網(wǎng)絡(luò)編程:第2卷:進(jìn)程間通信》第4章:管道和FIFO中可以看到。

我們還是來(lái)看一個(gè)管道的例子:

[root@stevens root]#? echo “hello world”|wc –c |wc –l

在這個(gè)例子中,有三個(gè)簡(jiǎn)單命令和兩個(gè)管道。

第一個(gè)命令是 echo “hello world”,它在屏幕上輸出 hello world。由于它后面是一個(gè)管道,因此,它并不在屏幕上輸出結(jié)果,而是把它的輸出重定向到管道的寫入端。

第二個(gè)命令是 wc –c,它本來(lái)需要指定輸入源,由于它前面是一個(gè)管道,因此它就從這個(gè)管道的讀出端讀數(shù)據(jù)。也就是說(shuō)讀到的是 hello world,wc –c 是統(tǒng)計(jì)讀到的字符數(shù),結(jié)果應(yīng)該是12。由于它后面又出現(xiàn)一個(gè)管道,因此這個(gè)結(jié)果不能輸出到屏幕上,而是重定向到第二個(gè)管道的寫入端。

第三個(gè)命令是 wc –l。它同樣從第二個(gè)管道的讀出端讀數(shù)據(jù),讀到的是12,然后它統(tǒng)計(jì)讀到了幾行數(shù)據(jù),結(jié)果是1行,于是在屏幕上輸出的最終結(jié)果是1。

在這個(gè)例子中,第一個(gè)命令只有一個(gè)“后”管道,第三個(gè)命令只有一個(gè)“前”管道,而第二個(gè)命令既有“前”管道,又有“后”管道。

在我們處理管道命令的do_pipe_cmd()函數(shù)中,它的處理過(guò)程是:

首先定義兩個(gè)管道 prefd 和 postfd,它們分別用來(lái)保存“前”管道和“后”管道。此外,還有一個(gè)變量 prepipe 來(lái)指示“前”管道是否有效。

然后依次檢查參數(shù)數(shù)組中每一個(gè)參數(shù),如果是管道符號(hào)(|),那么就認(rèn)為管道符號(hào)前面所有的參數(shù)組成了一個(gè)簡(jiǎn)單命令,并創(chuàng)建一個(gè)“后”管道。如果沒(méi)有“前”管道(管道中第一個(gè)簡(jiǎn)單命令是沒(méi)有“前”管道的),那么只傳遞“后”管道來(lái)調(diào)用do_simple_cmd(),否則,同時(shí)傳遞“前”管道和“后”管道來(lái)調(diào)用 do_simple_cmd()。

執(zhí)行完以后,用“前”管道來(lái)保存當(dāng)前的“后”管道,并設(shè)置“前”管道有效標(biāo)識(shí)prepipe,繼續(xù)往后掃描。如果掃描到最后,不再有管道符號(hào)出現(xiàn),那么只傳遞“前”管道來(lái)調(diào)用do_simple_cmd()。

代碼如下:

int i = 0, j = 0, prepipe = 0;

int prefd[2], postfd[2];

char* p;

while(argv[i]) {

?????? if(strcmp(argv[i], "|") == 0) { // pipe

????????????? p = argv[i];

????????????? argv[i] = 0;

????????????? pipe(postfd);???????? //create the post pipe

????????????? if(prepipe)?????

???????????????????? do_simple_cmd(i-j, argv+j, prefd, postfd);

????????????? else

???????????????????? do_simple_cmd(i-j, argv+j, 0, postfd);

????????????? argv[i] = p;

????????????? prepipe = 1;

????????????? prefd[0] = postfd[0];

????????????? prefd[1] = postfd[1];

????????????? j = ++i;

?????? } else

????????????? i++;

}

if(prepipe)

?????? do_simple_cmd(i-j, argv+j, prefd, 0);

else

?????? do_simple_cmd(i-j, argv+j, 0, 0);

最后,我們分析簡(jiǎn)單命令的處理過(guò)程。
簡(jiǎn)單命令處理過(guò)程

我們已經(jīng)看到,對(duì)列表命令和管道命令的處理,實(shí)際只是一個(gè)分解過(guò)程,最終命令的執(zhí)行還是要由簡(jiǎn)單命令來(lái)完成。

在簡(jiǎn)單命令的處理過(guò)程中,必須考慮以下情況:

1、區(qū)分內(nèi)部命令和外部命令

根據(jù)簡(jiǎn)單命令的定義,它的第一個(gè)參數(shù)是要執(zhí)行的命令,后面的參數(shù)作為該命令的參數(shù)。要執(zhí)行的命令有兩種情況:

一種是外部命令,也就是對(duì)應(yīng)著磁盤上的某個(gè)程序,例如 wc、ls等等。對(duì)這種外部命令,我們首先要到指定的路徑下找到它,然后再執(zhí)行它。

二是內(nèi)部命令,內(nèi)部命令并不對(duì)應(yīng)磁盤上的程序,例如cd、echo等等,它需要shell自己來(lái)決定該如何執(zhí)行。例如對(duì) cd 命令,shell就應(yīng)該根據(jù)它后面的參數(shù)改變當(dāng)前路徑。

對(duì)于外部命令,需要?jiǎng)?chuàng)建一個(gè)子進(jìn)程來(lái)執(zhí)行它,而對(duì)于內(nèi)部命令,則沒(méi)有這個(gè)必要。

外部命令的執(zhí)行,是通過(guò) exec 函數(shù)來(lái)完成的。有六種不同形式的 exec 函數(shù),它們可以統(tǒng)稱為 exec 函數(shù)。我們使用的是 execv()。關(guān)于 exec的細(xì)節(jié),請(qǐng)看《unix環(huán)境高級(jí)編程》第8章:進(jìn)程控制。

對(duì)于內(nèi)部命令,我們目前支持五種,分別是:

exit:退出shell解釋器

cd:改變目錄

echo:回顯

export:導(dǎo)入或顯示環(huán)境變量

history:顯示命令歷史信息

這幾個(gè)內(nèi)部命令分別由 do_exit()、do_cd()、do_echo()、do_export()、do_history()來(lái)實(shí)現(xiàn)。

2、處理重定向

在簡(jiǎn)單命令的定義中,包括了對(duì)重定向的支持。重定向有多種情況,最簡(jiǎn)單的是輸入重定向和輸出重定向,分別對(duì)應(yīng)著“<”和“>”。

輸入重定向,就是把“<”后面指定的文件作為標(biāo)準(zhǔn)輸入,例如:

wc < xxx

?????? 表示把 xxx 這個(gè)文件的內(nèi)容作為 wc 命令的輸入。

輸出重定向,就是把“>”后面指定的文件作為標(biāo)準(zhǔn)輸出,例如:

echo “hello world” > xxx

表示把 echo “hello world” 的結(jié)果輸入到 xxx 文件中,而不是屏幕上。

為了支持重定向,我們首先對(duì)簡(jiǎn)單命令的參數(shù)進(jìn)行掃描,如果遇到“<”或者“>”那么就認(rèn)為遇到了重定向,并把“<”或者“>”符號(hào)后面的參數(shù)作為重定向的文件名稱。

對(duì)于輸入重定向,首先是以只讀方式打開“<”后面的文件,并獲得文件描述符,然后將該文件描述符復(fù)制給標(biāo)準(zhǔn)輸入。

對(duì)于輸出重定向,首先是以寫方式打開“>”后面的文件,并獲得文件描述符,然后將該文件描述符復(fù)制給標(biāo)準(zhǔn)輸出。

具體實(shí)現(xiàn)在 predo_for_redirect() 函數(shù)中:

3、管道的實(shí)現(xiàn)

管道的實(shí)現(xiàn)實(shí)際上也是一種重定向的處理。對(duì)于“前”管道,類似于輸入重定向,不同的是,它是把一個(gè)指定的描述符(“前”管道的輸出端)復(fù)制給標(biāo)準(zhǔn)輸入。對(duì)于“后”管道,類似于輸出重定向,不同的是,它把一個(gè)指定的描述符(“后”管道的輸入端)復(fù)制給標(biāo)準(zhǔn)輸出。

在對(duì)管道的處理上,還必須要注意管道和輸入或輸出重定向同時(shí)出現(xiàn)的情況,如果是一個(gè)“前”管道和一個(gè)輸入重定向同時(shí)出現(xiàn),那么優(yōu)先處理輸入重定向,不再?gòu)摹扒啊惫艿乐凶x取數(shù)據(jù)了。同樣,如果一個(gè)“后”管道和一個(gè)輸出重定向同時(shí)出現(xiàn),那么優(yōu)先處理輸出重定向,不再把數(shù)據(jù)輸出到“后”管道中。

?????? 至此,我們已經(jīng)描述了實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 shell 解釋器的全部過(guò)程,相應(yīng)的代碼和 makefile 在我們的網(wǎng)站上可以下載。希望大家能夠結(jié)合代碼和這篇文章,親自動(dòng)手做一次,以加深對(duì)shell 解釋器的理解。

編寫自己的Shell解釋器


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!??!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 亚洲一区二区精品推荐 | 日本欧美一区二区三区乱码 | 99re7在线精品免费视频 | 亚洲欧美日韩成人网 | 97影院理论片手机在线观看 | 97视频免费在线观看 | 日本一级毛片2021免费 | 国产乱码亚洲精品一区二区 | 亚洲日韩成人 | 婷婷的五月| 国产高清国内精品福利色噜噜 | 俺来也俺来也天天夜夜视频 | 男人天堂免费 | 亚洲国产激情一区二区三区 | 亚洲欧美日韩v中文在线 | 九九伦理影院手机观看 | 深夜福利网站在线观看 | 亚洲综合色区图片区 | 日本中文一二区有码在线观看 | 黄色影院免费看 | 亚洲欧美卡通成人制服动漫 | 中文字幕三级久久久久久 | 韩日精品在线 | 亚洲欧美二区三区久本道 | 国产成人精品高清在线观看99 | 亚洲精品丝袜在线一区波多野结衣 | 奇米在线观看 | 欧美色精品天天在线观看视频 | 亚洲精彩 | 一级毛片卡 | 在线视频一区二区日韩国产 | 欧美日韩视频在线成人 | 天天艹天天射 | 麻豆成人久久精品二区三区小说 | 99亚洲乱人伦精品 | 亚洲国产成人久久精品hezyo | 色综合a怡红院怡红院首页 色综合h | 中文字幕日韩精品中文区 | 国产精品久久久久久麻豆一区 | www激情五月| 看全色黄大色大片免费久久久 |