之前為家介紹了關(guān)于如何構(gòu)建一個(gè)Linux Shell的一、二、三、四。今天這里為大家介紹的是關(guān)于如何構(gòu)建Linux Shell的教程的第五部分。正如我們?cè)谇懊娴牟糠种兴吹降模粋€(gè)簡(jiǎn)單的命令由一個(gè)或多個(gè)參數(shù)組成。第一個(gè)單詞包含我們要執(zhí)行的命令的名稱,其余單詞包含命令的參數(shù)。在外殼程序執(zhí)行命令之前,它將對(duì)命令字執(zhí)行字?jǐn)U展。
單詞擴(kuò)展是shell提取命令字,檢查它是否包含變量名,路徑名,命令和算術(shù)表達(dá)式,然后用其值替換每個(gè)名稱/命令/表達(dá)式的過程。
通常長(zhǎng)于原始單詞的結(jié)果單詞可以在稱為字段拆分的過程中分解為一個(gè)或多個(gè)子單詞。
在這一部分中,我們將實(shí)現(xiàn)POSIX定義的7個(gè)詞擴(kuò)展,它們是:波浪號(hào)擴(kuò)展,參數(shù)擴(kuò)展,算術(shù)擴(kuò)展,命令替換,字段拆分,路徑名擴(kuò)展和引號(hào)刪除。還有其他單詞擴(kuò)展,例如大括號(hào)擴(kuò)展和流程替換,這些沒有由POSIX定義,因此我們將不在這里討論。完成本課程后,如果您通過實(shí)現(xiàn)非POSIX單詞擴(kuò)展來擴(kuò)展Shell,將是一個(gè)很好的練習(xí)。
單詞擴(kuò)展過程
當(dāng)外殼程序執(zhí)行單詞擴(kuò)展時(shí),它將檢查命令行中的每個(gè)單詞以查看其是否包含可能的單詞擴(kuò)展。擴(kuò)展可以出現(xiàn)在單詞的任何位置:在單詞的開頭,中間或結(jié)尾。擴(kuò)展可能還包括整個(gè)單詞。
單詞擴(kuò)展之前有一個(gè)$標(biāo)志。后面的字符$符號(hào)指示外殼要執(zhí)行的擴(kuò)展類型。這些字符由外殼解釋如下:
一個(gè)或多個(gè)數(shù)字,指示位置參數(shù)的變量擴(kuò)展。
之一@,*,#,?,-,$,!,要么0,它指示特殊參數(shù)的變量擴(kuò)展。
一個(gè)或多個(gè)字母數(shù)字字符和/或下劃線,以字母或下劃線開頭,表示外殼變量名稱。
括號(hào)內(nèi)的變量名{和}。
算術(shù)展開,被(和)。
命令替換,由((和))。
Shell首先執(zhí)行波浪號(hào)擴(kuò)展,參數(shù)擴(kuò)展,命令替換和算術(shù)擴(kuò)展,然后進(jìn)行字段拆分和路徑名擴(kuò)展。最后,shell從已擴(kuò)展單詞中刪除了已成為原始單詞一部分的所有引號(hào)字符。
使用文字
當(dāng)外殼程序執(zhí)行單詞擴(kuò)展時(shí),該過程可能會(huì)導(dǎo)致零個(gè),一個(gè)或多個(gè)單詞。
structword_s
{
char*data;
intlen;
structword_s*next;
};
該結(jié)構(gòu)包含以下字段:
data=>表示此單詞的字符串。
len=>的長(zhǎng)度data領(lǐng)域。
next=>指向下一個(gè)單詞的指針,或者NULL如果這是最后一個(gè)單詞。
當(dāng)然,我們需要一些函數(shù)來分配和釋放我們的structword_s結(jié)構(gòu)。為此,我們將使用以下功能:
structword_s*make_word(char*str);
voidfree_all_words(structword_s*first);
第一個(gè)函數(shù)為該結(jié)構(gòu)分配內(nèi)存,創(chuàng)建單詞字符串的副本,然后返回新分配的單詞。第二個(gè)功能釋放單詞結(jié)構(gòu)列表使用的內(nèi)存。您可以在我們的網(wǎng)站中閱讀功能代碼wordexp.c源文件。
定義一些助手功能
如前所述,詞擴(kuò)展是一個(gè)復(fù)雜的過程,為此,我們需要定義許多不同的功能。在深入研究單詞擴(kuò)展的細(xì)節(jié)之前,讓我們先定義一些輔助函數(shù)。
以下列表顯示了我們的輔助函數(shù)的函數(shù)原型,所有這些我們將在wordexp.c源文件:
char*wordlist_to_str(structword_s*word);
voiddelete_char_at(char*str,size_tindex);
intis_name(char*str);
size_tfind_closing_quote(char*data);
size_tfind_closing_brace(char*data);
char*substitute_str(char*s1,char*s2,size_tstart,size_tend);
intsubstitute_word(char**pstart,char**p,size_tlen,char*(func)(char*),intadd_quotes);
以下是這些函數(shù)的功能細(xì)目:
wordlist_to_str()=>將擴(kuò)展單詞的鏈接列表轉(zhuǎn)換為單個(gè)字符串。
delete_char_at()=>從字符串中刪除給定索引處的字符。
is_name()=>檢查字符串是否表示有效的變量名。
find_closing_quote()=>當(dāng)單詞擴(kuò)展包含開頭的引號(hào)",',要么`,我們需要找到匹配的用引號(hào)引起來的字符,該字符將引號(hào)引起來的字符串括起來。此函數(shù)返回單詞中結(jié)束引號(hào)字符的從零開始的索引。
find_closing_brace()=>與上面類似,不同之處在于它找到匹配的右括號(hào)。也就是說,如果左括號(hào)是{,(,要么[,此函數(shù)返回匹配項(xiàng)的從零開始的索引},),要么]字符。查找引號(hào)對(duì)對(duì)于處理參數(shù)擴(kuò)展,算術(shù)擴(kuò)展和命令替換很重要。
substitute_str()=>替換的子字符串s1,從位置上的角色開始start到那個(gè)位置end,s2串。當(dāng)單詞擴(kuò)展是較長(zhǎng)單詞的一部分例如,${PATH}/ls,在這種情況下,我們只需要擴(kuò)展${PATH},然后附加/ls到擴(kuò)展字符串的末尾。
substitute_word()=>一個(gè)幫助程序函數(shù),它調(diào)用其他單詞擴(kuò)展功能,我們將在以下各節(jié)中對(duì)其進(jìn)行定義。
另外,我們將定義一些函數(shù)來幫助我們處理字符串。我們將在strings.c源文件:
char*strchr_any(char*string,char*chars);
char*quote_val(char*val,intadd_quotes);
intcheck_buffer_bounds(int*count,int*len,char***buf);
voidfree_buffer(intlen,char**buf);
這些功能的作用如下:
strchr_any()=>類似于strchr(),除了它會(huì)在給定的字符串中搜索任何給定的字符。
quote_val()=>執(zhí)行與引號(hào)刪除相反的操作,即將字符串轉(zhuǎn)換為帶引號(hào)的字符串。
的check_buffer_bounds()和free_buffer()函數(shù)將使我們的后端執(zhí)行器支持可變數(shù)量的命令參數(shù),而不是我們?cè)诘诙糠种性O(shè)置的硬限制255。
現(xiàn)在,讓我們編寫函數(shù)來處理每種類型的單詞擴(kuò)展。
波浪號(hào)擴(kuò)展
在波浪符號(hào)擴(kuò)展期間,外殼程序?qū)⒉ɡ朔?hào)字符替換為用戶主目錄的路徑名。例如,~和~/波浪線擴(kuò)展到當(dāng)前用戶的主目錄,而~john被波浪擴(kuò)展到用戶John的主目錄,依此類推。除后面的所有字符之外,代字號(hào)字符被稱為代字號(hào)前綴
要執(zhí)行波浪線擴(kuò)展,我們將定義tilde_expand()函數(shù),具有以下原型:
char*tilde_expand(char*s);
該函數(shù)接受一個(gè)參數(shù):我們要擴(kuò)展的代字號(hào)前綴。如果擴(kuò)展成功,該函數(shù)返回一個(gè)的malloc表示波浪線擴(kuò)展前綴“d字符串。否則返回NULL。下面是該函數(shù)為擴(kuò)展代字號(hào)前綴所做的工作的快速分解:
如果前綴是~,獲得$HOME外殼變量。如果$HOME被定義而不是NULL,返回其值。否則,通過調(diào)用獲取當(dāng)前的用戶ID(UID)getuid(),然后將UID傳遞給getpwuid()獲取與當(dāng)前用戶相對(duì)應(yīng)的密碼數(shù)據(jù)庫(kù)條目。的pw_dir密碼數(shù)據(jù)庫(kù)條目的“字段”包含函數(shù)返回的主目錄的路徑名。
如果前綴包含其他字符除了前導(dǎo)字符~,我們將這些字母作為要獲取其主目錄的用戶的名稱。我們稱之為getpwnam(),將其傳遞給用戶名,然后返回pw_dir領(lǐng)域。
如果我們無法檢索主目錄,則返回NULL。否則,我們將返回主目錄路徑的malloc副本。
參數(shù)擴(kuò)展
在參數(shù)擴(kuò)展中,外殼程序用變量的值替換外殼程序變量的名稱。參數(shù)擴(kuò)展使外殼程序可以執(zhí)行諸如echo$PATH。在此示例中,外殼程序?qū)?PATH變量,將其替換為實(shí)際的可執(zhí)行路徑。
為了向shell發(fā)出我們要擴(kuò)展shell變量的信號(hào),我們?cè)谧兞棵Q前添加一個(gè)$標(biāo)志。也就是說,擴(kuò)大PATH,USER和SHELL變量,我們需要傳遞$PATH,$USER和$SHELL將單詞分別傳遞給shell或者,我們可以將這些變量擴(kuò)展傳遞給shellshell,如下所示:${PATH},${USER}和${SHELL}。Shell變量名稱可以包含字母,數(shù)字和下劃線的任意組合。名稱可以包含大寫字母或小寫字母,盡管按照慣例,大寫名稱保留用于標(biāo)準(zhǔn)Shell變量。
我們可以使用參數(shù)擴(kuò)展修飾符來控制外殼如何執(zhí)行參數(shù)擴(kuò)展,該修飾符告訴外殼我們要擴(kuò)展值的哪一部分,以及在沒有給定名稱的外殼變量的情況下該怎么做。下表總結(jié)了參數(shù)擴(kuò)展修飾符由POSIX定義的修飾符在“描述”列中由POSIX單詞標(biāo)記。大多數(shù)外殼程序都支持其他修飾符,我們將不在此處討論。有關(guān)非POSIX修飾符的更多信息,請(qǐng)參見您的Shell的手冊(cè)頁(yè)。
要執(zhí)行參數(shù)擴(kuò)展,我們將定義var_expand()函數(shù),具有以下原型:
char*var_expand(char*orig_var_name);
該函數(shù)接受一個(gè)參數(shù):我們要擴(kuò)展的參數(shù)。如果擴(kuò)展成功,該函數(shù)將返回一個(gè)包含擴(kuò)展值的malloc'd字符串。否則返回NULL。下面是該函數(shù)為擴(kuò)展變量名以獲取其值而執(zhí)行的操作的快速細(xì)分:
如果變量名用大括號(hào)括起來,請(qǐng)刪除大括號(hào),因?yàn)樗鼈儾皇亲兞棵旧淼囊徊糠帧H绻Q以#,我們需要獲取變量名稱的長(zhǎng)度。
如果變量名稱包含冒號(hào),我們將使用它來將名稱與單詞或模式分開。單詞或圖案的使用如上表所示。獲取具有給定變量名稱的符號(hào)表?xiàng)l目。獲取符號(hào)表?xiàng)l目的值。
如果該值為空或?yàn)榭眨?qǐng)使用擴(kuò)展中提供的替代詞。
如果該值不為空,則將該值用作擴(kuò)展結(jié)果。要使外殼執(zhí)行模式匹配${parameter#word}和${parameter%word}擴(kuò)展,我們需要兩個(gè)幫助函數(shù):match_suffix()和match_prefix()。我們不會(huì)在這里討論這些功能,但是您可以從此鏈接中閱讀它們的代碼。
如果擴(kuò)展修飾符為${parameter:=word},我們需要將符號(hào)表?xiàng)l目的值設(shè)置為剛剛擴(kuò)展的值。
如果擴(kuò)展以#,獲取擴(kuò)展值的長(zhǎng)度并將其用作最終結(jié)果。返回?cái)U(kuò)展值的malloc'd副本或其長(zhǎng)度,視情況而定。
命令替換
在命令替換中,shell派生一個(gè)進(jìn)程來運(yùn)行命令,然后用命令的輸出替換命令替換擴(kuò)展。例如,在以下循環(huán)中:
foriin$(ls);doecho$i;done
外殼分叉一個(gè)過程,其中l(wèi)s命令運(yùn)行。該命令的輸出是當(dāng)前目錄中的文件列表。Shell獲取該輸出,將其拆分為單詞列表,然后一次將這些單詞提供給循環(huán)。在循環(huán)的每次迭代中,變量$i被分配了列表中下一個(gè)文件的名稱。
此名稱將傳遞給echo命令,該命令在單獨(dú)的行上輸出名稱。
命令替換可以寫成$(command),要么`command`。要執(zhí)行命令替換,我們將定義command_substitute()函數(shù),具有以下原型:
char*command_substitute(char*orig_cmd);
該函數(shù)接受一個(gè)參數(shù):我們要執(zhí)行的命令。如果擴(kuò)展成功,則該函數(shù)將返回一個(gè)malloc'd字符串,表示命令的輸出。如果擴(kuò)展失敗,或者命令沒有輸出任何內(nèi)容,則函數(shù)返回NULL。
下面是該函數(shù)為擴(kuò)展命令替換而執(zhí)行的操作的快速細(xì)分:
根據(jù)使用的格式,我們首先刪除$()或反引號(hào)``。這給我們留下了我們需要執(zhí)行的命令。
呼叫popen()創(chuàng)建一個(gè)管道。我們將要執(zhí)行的命令傳遞給popen(),我們得到一個(gè)指向FILE流,我們將從中讀取命令的輸出。
呼叫fread()從管道讀取命令的輸出。將讀取的字符串存儲(chǔ)在緩沖區(qū)中。
刪除所有尾隨換行符。
關(guān)閉管道,并使用命令輸出返回緩沖區(qū)。
算術(shù)擴(kuò)展
使用算術(shù)擴(kuò)展,我們可以讓外殼執(zhí)行不同的算術(shù)運(yùn)算,并將結(jié)果用于執(zhí)行其他命令。盡管POSIX要求外殼程序僅支持帶符號(hào)的長(zhǎng)整數(shù)算法,但許多外殼程序都支持浮點(diǎn)算法。
此外,盡管大多數(shù)外殼程序都不需要外殼程序來支持任何數(shù)學(xué)函數(shù)。對(duì)于簡(jiǎn)單的shell,我們將僅支持帶符號(hào)的長(zhǎng)整數(shù)算法,而沒有數(shù)學(xué)函數(shù)支持。
算術(shù)擴(kuò)展寫為$((expression))。
要執(zhí)行擴(kuò)展,我們將定義arithm_expand()函數(shù),具有以下原型:
char*arithm_expand(char*expr);
的arithm_expand()函數(shù)接收包含算術(shù)表達(dá)式的字符串,執(zhí)行必要的計(jì)算,然后以malloc'd字符串的形式返回結(jié)果。該函數(shù)及其相關(guān)的幫助器函數(shù)既復(fù)雜又冗長(zhǎng),但這是主要亮點(diǎn):
算術(shù)表達(dá)式轉(zhuǎn)換為反向波蘭表示法,更易于分析和計(jì)算。RPN由一系列算術(shù)運(yùn)算組成,其中運(yùn)算符遵循其操作數(shù)。例如,RPNx-y是xy-,以及3+4×(2?1)是3421?×+。
在轉(zhuǎn)換過程中,算術(shù)運(yùn)算符被壓入一個(gè)運(yùn)算符堆棧,我們將從中彈出每個(gè)運(yùn)算符并稍后執(zhí)行其運(yùn)算。同樣,操作數(shù)被添加到它們自己的堆棧中。
一次將操作員從堆棧中彈出,然后對(duì)操作員進(jìn)行檢查。根據(jù)運(yùn)算符的類型,一個(gè)或兩個(gè)操作數(shù)從堆棧中彈出。控制此過程的規(guī)則是調(diào)車場(chǎng)算法的規(guī)則,您可以在此處閱讀。
結(jié)果將轉(zhuǎn)換為字符串,然后將其返回給調(diào)用方。
場(chǎng)分裂
在字段拆分期間,shell會(huì)獲取參數(shù)擴(kuò)展,命令替換和算術(shù)擴(kuò)展的結(jié)果,并將它們拆分為一個(gè)或多個(gè)部分,我們將其稱為字段。該過程取決于$IFS外殼變量。IFS是一個(gè)歷史術(shù)語(yǔ),代表內(nèi)部字段分隔符,它起源于Unixshell沒有內(nèi)置數(shù)組類型的時(shí)間。
作為解決方法,早期的Unixshell必須找到另一種表示多成員數(shù)組的方式。外殼程序?qū)⒁詥蝹€(gè)字符串將數(shù)組成員連接在一起,并以空格分隔。當(dāng)外殼程序需要檢索數(shù)組成員時(shí),它將字符串分成一個(gè)或多個(gè)字段。的$IFS變量告訴外殼程序在何處確切地中斷該字符串。
外殼解釋$IFS如下字符:
如果值$IFS是空格,制表符和換行符,或者如果未設(shè)置變量,則在輸入的開頭或結(jié)尾處的空格,制表符或換行符的任何序列都將被忽略,并且輸入中這些字符的任何序列應(yīng)定界領(lǐng)域。
如果值$IFS為null,不得執(zhí)行任何字段拆分。
否則,應(yīng)依次適用以下規(guī)則:(a)$IFS在輸入的開頭和結(jié)尾應(yīng)忽略空格。(b)輸入中的每次出現(xiàn)$IFS不是的字符$IFS空白,以及任何相鄰的空白$IFS如前所述,空白應(yīng)界定一個(gè)字段。(c)非零長(zhǎng)度$IFS空白應(yīng)界定一個(gè)字段。
要執(zhí)行擴(kuò)展,我們將定義field_split()函數(shù),具有以下原型:
structword_s*field_split(char*str);
路徑名擴(kuò)展
在擴(kuò)展路徑名期間,shell將以給定的模式匹配一個(gè)或多個(gè)文件名。除特殊字符外,該模式還可以包含普通字符*,?和[],也稱為“通配符”。
星號(hào)*匹配任意長(zhǎng)度的字符,匹配一個(gè)字符,并且方括號(hào)引入正則表達(dá)式(RE)括號(hào)表達(dá)式。擴(kuò)展的結(jié)果是名稱與模式匹配的文件列表。
要執(zhí)行擴(kuò)展,我們將定義pathnames_expand()函數(shù),具有以下原型:
structword_s*pathnames_expand(structword_s*words);
此函數(shù)接受一個(gè)參數(shù):指向我們要路徑名擴(kuò)展的單詞的鏈接列表中第一個(gè)單詞的指針。對(duì)于每個(gè)單詞,該函數(shù)執(zhí)行以下操作:
檢查單詞是否包含任何通配符*,?和[],通過調(diào)用輔助函數(shù)has_glob_chars(),我們將在源文件中定義pattern.c。如果單詞包含通配符,我們將其視為需要匹配的模式;否則,我們移至下一個(gè)單詞。
獲取名稱與模式匹配的文件列表,不包括特殊名稱.和..。我們將模式匹配委托給另一個(gè)幫助函數(shù),get_filename_matches(),我們將在同一源文件中定義pattern.c。
將匹配的文件名添加到最終列表。
移至下一個(gè)單詞并循環(huán)。
返回與所有給定單詞匹配的文件名列表。
刪除報(bào)價(jià)
單詞擴(kuò)展過程的最后一步是刪除引號(hào)。引用用于刪除某些字符到shell的特殊含義。外殼會(huì)以特殊方式處理某些字符,例如反斜杠和引號(hào)。要禁止這種行為,我們需要引用那些字符以強(qiáng)制外殼將它們視為普通字符。
我們可以使用以下三種方式之一對(duì)字符進(jìn)行引用:使用反斜杠,單引號(hào)或雙引號(hào)。反斜杠字符用于保留反斜杠后面的字符的字面意思。這類似于我們用C語(yǔ)言轉(zhuǎn)義字符的方式。
單引號(hào)保留引號(hào)內(nèi)所有字符的字面含義,即外殼程序不嘗試對(duì)單引號(hào)字符串進(jìn)行單詞擴(kuò)展。
雙引號(hào)與單引號(hào)類似,不同之處在于外殼可以識(shí)別反引號(hào),反斜杠和$標(biāo)志。也就是說,外殼程序可以在雙引號(hào)字符串內(nèi)執(zhí)行單詞擴(kuò)展。
要執(zhí)行報(bào)價(jià)刪除,我們將定義remove_quotes()函數(shù),具有以下原型:
voidremove_quotes(structword_s*wordlist)。
放在一起
現(xiàn)在我們有了詞擴(kuò)展功能,是時(shí)候?qū)⑵浣Y(jié)合在一起了。在本節(jié)中,我們將編寫主要的單詞擴(kuò)展功能,我們將調(diào)用該功能來執(zhí)行單詞擴(kuò)展。反過來,此函數(shù)將調(diào)用其他函數(shù)來執(zhí)行單詞擴(kuò)展的各個(gè)步驟。
我們的主要功能是word_expand(),我們將在源文件中定義wordexp.c:
structword_s*word_expand(char*orig_word);
這是為了對(duì)作為唯一參數(shù)傳遞的單詞執(zhí)行單詞擴(kuò)展的功能:
創(chuàng)建原始單詞的副本。我們將在此副本上執(zhí)行單詞擴(kuò)展,以便在出現(xiàn)任何問題時(shí)將原始單詞保留完整。逐字掃描單詞,尋找特殊字符~,",',`,=,和$。如果找到上述字符之一,請(qǐng)致電substitute_word(),這將調(diào)用相應(yīng)的單詞擴(kuò)展功能。
跳過任何沒有特殊含義的字符。完成單詞擴(kuò)展后,通過調(diào)用執(zhí)行字段拆分field_split()。通過調(diào)用執(zhí)行路徑名擴(kuò)展pathnames_expand()。通過調(diào)用執(zhí)行報(bào)價(jià)刪除remove_quotes()。返回?cái)U(kuò)展單詞的列表。
更新掃描儀
在本教程的第二部分中,我們編寫了tokenize()函數(shù),我們用來獲取輸入令牌。到目前為止,我們的tokenize()函數(shù)不知道如何處理帶引號(hào)的字符串和轉(zhuǎn)義字符。要添加此功能,我們需要更新代碼。打開scanner.c文件,并將以下代碼添加到tokenize()功能之后switch聲明的開頭括號(hào):
case'"':
case''':
case'`':
add_to_buf(nc);
i=find_closing_quote(src->buffer+src->curpos);
if(!i)
{
src->curpos=src->bufsize;
fprintf(stderr,"error:missingclosingquote'%c' ",nc);
return&eof_token;
}
while(i--)
{
add_to_buf(next_char(src));
}
break;
case'\':
nc2=next_char(src);
if(nc2==' ')
{
break;
}
add_to_buf(nc);
if(nc2>0)
{
add_to_buf(nc2);
}
break;
case'$':
add_to_buf(nc);
nc=peek_char(src);
if(nc=='{'||nc=='(')
{
i=find_closing_brace(src->buffer+src->curpos+1);
if(!i)
{
src->curpos=src->bufsize;
fprintf(stderr,"error:missingclosingbrace'%c' ",nc);
return&eof_token;
}
while(i--)
{
add_to_buf(next_char(src));
}
}
elseif(isalnum(nc)||nc=='*'||nc=='@'||nc=='#'||
nc=='!'||nc=='?'||nc=='$')
{
add_to_buf(next_char(src));
}
break;
現(xiàn)在,我們的詞法掃描器知道如何識(shí)別和跳過帶引號(hào)的字符串,轉(zhuǎn)義字符和其他單詞擴(kuò)展構(gòu)造。在此鏈接中查看更新的詞法掃描程序代碼。
更新執(zhí)行器最后,我們需要更新后端執(zhí)行程序,以便可以:
在執(zhí)行命令之前,對(duì)命令的參數(shù)執(zhí)行單詞擴(kuò)展。每個(gè)命令支持超過255個(gè)參數(shù)。打開executor.c文件,導(dǎo)航到do_simple_command()函數(shù)并找到以下幾行:
intargc=0;
longmax_args=255;
char*argv[max_args+1];
char*str;
while(child)
{
...
}
argv[argc]=NULL;
并用以下代碼替換它們:
intargc=0;
inttargc=0;
char**argv=NULL;
char*str;
while(child)
{
str=child->val.str;
structword_s*w=word_expand(str);
if(!w)
{
child=child->next_sibling;
continue;
}
structword_s*w2=w;
while(w2)
{
if(check_buffer_bounds(&argc,&targc,&argv))
{
str=malloc(strlen(w2->data)+1);
if(str)
{
strcpy(str,w2->data);
argv[argc++]=str;
}
}
w2=w2->next;
}
free_all_words(w);
child=child->next_sibling;
}
if(check_buffer_bounds(&argc,&targc,&argv))
{
argv[argc]=NULL;
}
使用此代碼,執(zhí)行程序調(diào)用word_expand()在每個(gè)命令自變量上,并將擴(kuò)展的單詞添加到自變量列表,我們最終將其傳遞給命令。該列表可以根據(jù)需要增長(zhǎng),這要?dú)w功于我們check_buffer_bounds()函數(shù),根據(jù)需要將內(nèi)存分配給緩沖區(qū)。
現(xiàn)在剩下的就是在執(zhí)行完命令后釋放參數(shù)列表,然后返回調(diào)用者。為此我們致電:
free_buffer(argc,argv);
在三個(gè)不同的位置:執(zhí)行內(nèi)置實(shí)用程序后,如果fork()返回錯(cuò)誤狀態(tài),然后waitpid()已經(jīng)回來了。在此鏈接中查看更新的執(zhí)行程序代碼。
編譯外殼
讓我們編譯一下shell。打開您喜歡的終端模擬器,導(dǎo)航到源目錄,并確保其中有19個(gè)文件和2個(gè)子目錄:
現(xiàn)在,使用以下命令編譯外殼程序:
make
如果一切順利gcc不應(yīng)輸出任何內(nèi)容,并且應(yīng)該有一個(gè)名為shell在當(dāng)前目錄中:
現(xiàn)在通過運(yùn)行來調(diào)用shell./shell,然后嘗試使用我們的單詞擴(kuò)展功能并檢查結(jié)果:$echo*Makefilebuildbuiltinsexecutor.cexecutor.hinitsh.cmain.cnode.cnode.hparser.cparser.hpattern.cprompt.cscanner.cscanner.hshellshell.hshunt.csource.csource.hstrings.csymtabwordexp.c
$echo'*'
*
$echo~
/home/user
$echo~/Downloads
/home/user/Downloads
$echo${A=value}
value
$echo$A
value
$echo$((2+7))
9
就是今天。我們的外殼現(xiàn)在可以處理各種單詞擴(kuò)展。玩弄外殼,看看從不同類型的單詞擴(kuò)展中可以得到什么結(jié)果。將結(jié)果與從默認(rèn)Shell獲得的結(jié)果進(jìn)行比較。
在此,我們已經(jīng)取得了長(zhǎng)足的進(jìn)步,提供了許多代碼,其中大多數(shù)代碼沒有時(shí)間或空間來詳細(xì)研究。您可能需要花一些時(shí)間閱讀我們存儲(chǔ)庫(kù)中的代碼,以使自己熟悉單詞擴(kuò)展過程。想了解更多關(guān)于Linux Shell 的信息,請(qǐng)繼續(xù)關(guān)注中培偉業(yè)。