日韩av片子_国产自在自线午夜精品视频在_使劲快高潮了国语对白在线_久久免费毛片大全_激情丁香综合_欧美成人精品欧美一级乱黄码

中培偉業IT資訊頻道
您現在的位置:首頁 > IT資訊 > IT運維 > 如何構建一個Linux Shell(五)

如何構建一個Linux Shell(五)

2020-08-31 18:47:59 | 來源:中培企業IT培訓網

之前為家介紹了關于如何構建一個Linux Shell的一、二、三、四。今天這里為大家介紹的是關于如何構建Linux Shell的教程的第五部分。正如我們在前面的部分中所看到的,一個簡單的命令由一個或多個參數組成。第一個單詞包含我們要執行的命令的名稱,其余單詞包含命令的參數。在外殼程序執行命令之前,它將對命令字執行字擴展。

單詞擴展是shell提取命令字,檢查它是否包含變量名,路徑名,命令和算術表達式,然后用其值替換每個名稱/命令/表達式的過程。

通常長于原始單詞的結果單詞可以在稱為字段拆分的過程中分解為一個或多個子單詞。

在這一部分中,我們將實現POSIX定義的7個詞擴展,它們是:波浪號擴展,參數擴展,算術擴展,命令替換,字段拆分,路徑名擴展和引號刪除。還有其他單詞擴展,例如大括號擴展和流程替換,這些沒有由POSIX定義,因此我們將不在這里討論。完成本課程后,如果您通過實現非POSIX單詞擴展來擴展Shell,將是一個很好的練習。

  單詞擴展過程

當外殼程序執行單詞擴展時,它將檢查命令行中的每個單詞以查看其是否包含可能的單詞擴展。擴展可以出現在單詞的任何位置:在單詞的開頭,中間或結尾。擴展可能還包括整個單詞。

單詞擴展之前有一個$標志。后面的字符$符號指示外殼要執行的擴展類型。這些字符由外殼解釋如下:

一個或多個數字,指示位置參數的變量擴展。

之一@,*,#,?,-,$,!,要么0,它指示特殊參數的變量擴展。

一個或多個字母數字字符和/或下劃線,以字母或下劃線開頭,表示外殼變量名稱。

括號內的變量名{和}。

算術展開,被(和)。

命令替換,由((和))。

Shell首先執行波浪號擴展,參數擴展,命令替換和算術擴展,然后進行字段拆分和路徑名擴展。最后,shell從已擴展單詞中刪除了已成為原始單詞一部分的所有引號字符。

  使用文字

當外殼程序執行單詞擴展時,該過程可能會導致零個,一個或多個單詞。

structword_s

{

char*data;

intlen;

structword_s*next;

};

  該結構包含以下字段:

data=>表示此單詞的字符串。

len=>的長度data領域。

next=>指向下一個單詞的指針,或者NULL如果這是最后一個單詞。

  當然,我們需要一些函數來分配和釋放我們的structword_s結構。為此,我們將使用以下功能:

structword_s*make_word(char*str);

voidfree_all_words(structword_s*first);

第一個函數為該結構分配內存,創建單詞字符串的副本,然后返回新分配的單詞。第二個功能釋放單詞結構列表使用的內存。您可以在我們的網站中閱讀功能代碼wordexp.c源文件。

  定義一些助手功能

如前所述,詞擴展是一個復雜的過程,為此,我們需要定義許多不同的功能。在深入研究單詞擴展的細節之前,讓我們先定義一些輔助函數。

以下列表顯示了我們的輔助函數的函數原型,所有這些我們將在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);

以下是這些函數的功能細目:

wordlist_to_str()=>將擴展單詞的鏈接列表轉換為單個字符串。

delete_char_at()=>從字符串中刪除給定索引處的字符。

is_name()=>檢查字符串是否表示有效的變量名。

find_closing_quote()=>當單詞擴展包含開頭的引號",',要么`,我們需要找到匹配的用引號引起來的字符,該字符將引號引起來的字符串括起來。此函數返回單詞中結束引號字符的從零開始的索引。

find_closing_brace()=>與上面類似,不同之處在于它找到匹配的右括號。也就是說,如果左括號是{,(,要么[,此函數返回匹配項的從零開始的索引},),要么]字符。查找引號對對于處理參數擴展,算術擴展和命令替換很重要。

substitute_str()=>替換的子字符串s1,從位置上的角色開始start到那個位置end,s2串。當單詞擴展是較長單詞的一部分例如,${PATH}/ls,在這種情況下,我們只需要擴展${PATH},然后附加/ls到擴展字符串的末尾。

substitute_word()=>一個幫助程序函數,它調用其他單詞擴展功能,我們將在以下各節中對其進行定義。

另外,我們將定義一些函數來幫助我們處理字符串。我們將在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(),除了它會在給定的字符串中搜索任何給定的字符。

quote_val()=>執行與引號刪除相反的操作,即將字符串轉換為帶引號的字符串。

的check_buffer_bounds()和free_buffer()函數將使我們的后端執行器支持可變數量的命令參數,而不是我們在第二部分中設置的硬限制255。

現在,讓我們編寫函數來處理每種類型的單詞擴展。

  波浪號擴展

在波浪符號擴展期間,外殼程序將波浪符號字符替換為用戶主目錄的路徑名。例如,~和~/波浪線擴展到當前用戶的主目錄,而~john被波浪擴展到用戶John的主目錄,依此類推。除后面的所有字符之外,代字號字符被稱為代字號前綴

要執行波浪線擴展,我們將定義tilde_expand()函數,具有以下原型:

char*tilde_expand(char*s);

該函數接受一個參數:我們要擴展的代字號前綴。如果擴展成功,該函數返回一個的malloc表示波浪線擴展前綴“d字符串。否則返回NULL。下面是該函數為擴展代字號前綴所做的工作的快速分解:

如果前綴是~,獲得$HOME外殼變量。如果$HOME被定義而不是NULL,返回其值。否則,通過調用獲取當前的用戶ID(UID)getuid(),然后將UID傳遞給getpwuid()獲取與當前用戶相對應的密碼數據庫條目。的pw_dir密碼數據庫條目的“字段”包含函數返回的主目錄的路徑名。

如果前綴包含其他字符除了前導字符~,我們將這些字母作為要獲取其主目錄的用戶的名稱。我們稱之為getpwnam(),將其傳遞給用戶名,然后返回pw_dir領域。

如果我們無法檢索主目錄,則返回NULL。否則,我們將返回主目錄路徑的malloc副本。

  參數擴展

在參數擴展中,外殼程序用變量的值替換外殼程序變量的名稱。參數擴展使外殼程序可以執行諸如echo$PATH。在此示例中,外殼程序對$PATH變量,將其替換為實際的可執行路徑。

為了向shell發出我們要擴展shell變量的信號,我們在變量名稱前添加一個$標志。也就是說,擴大PATH,USER和SHELL變量,我們需要傳遞$PATH,$USER和$SHELL將單詞分別傳遞給shell或者,我們可以將這些變量擴展傳遞給shellshell,如下所示:${PATH},${USER}和${SHELL}。Shell變量名稱可以包含字母,數字和下劃線的任意組合。名稱可以包含大寫字母或小寫字母,盡管按照慣例,大寫名稱保留用于標準Shell變量。

我們可以使用參數擴展修飾符來控制外殼如何執行參數擴展,該修飾符告訴外殼我們要擴展值的哪一部分,以及在沒有給定名稱的外殼變量的情況下該怎么做。下表總結了參數擴展修飾符由POSIX定義的修飾符在“描述”列中由POSIX單詞標記。大多數外殼程序都支持其他修飾符,我們將不在此處討論。有關非POSIX修飾符的更多信息,請參見您的Shell的手冊頁。

要執行參數擴展,我們將定義var_expand()函數,具有以下原型:

char*var_expand(char*orig_var_name);

該函數接受一個參數:我們要擴展的參數。如果擴展成功,該函數將返回一個包含擴展值的malloc'd字符串。否則返回NULL。下面是該函數為擴展變量名以獲取其值而執行的操作的快速細分:

如果變量名用大括號括起來,請刪除大括號,因為它們不是變量名本身的一部分。如果名稱以#,我們需要獲取變量名稱的長度。

如果變量名稱包含冒號,我們將使用它來將名稱與單詞或模式分開。單詞或圖案的使用如上表所示。獲取具有給定變量名稱的符號表條目。獲取符號表條目的值。

如果該值為空或為空,請使用擴展中提供的替代詞。

如果該值不為空,則將該值用作擴展結果。要使外殼執行模式匹配${parameter#word}和${parameter%word}擴展,我們需要兩個幫助函數:match_suffix()和match_prefix()。我們不會在這里討論這些功能,但是您可以從此鏈接中閱讀它們的代碼。

如果擴展修飾符為${parameter:=word},我們需要將符號表條目的值設置為剛剛擴展的值。

如果擴展以#,獲取擴展值的長度并將其用作最終結果。返回擴展值的malloc'd副本或其長度,視情況而定。

  命令替換

在命令替換中,shell派生一個進程來運行命令,然后用命令的輸出替換命令替換擴展。例如,在以下循環中:

foriin$(ls);doecho$i;done

外殼分叉一個過程,其中ls命令運行。該命令的輸出是當前目錄中的文件列表。Shell獲取該輸出,將其拆分為單詞列表,然后一次將這些單詞提供給循環。在循環的每次迭代中,變量$i被分配了列表中下一個文件的名稱。

此名稱將傳遞給echo命令,該命令在單獨的行上輸出名稱。

命令替換可以寫成$(command),要么`command`。要執行命令替換,我們將定義command_substitute()函數,具有以下原型:

char*command_substitute(char*orig_cmd);

該函數接受一個參數:我們要執行的命令。如果擴展成功,則該函數將返回一個malloc'd字符串,表示命令的輸出。如果擴展失敗,或者命令沒有輸出任何內容,則函數返回NULL。

下面是該函數為擴展命令替換而執行的操作的快速細分:

根據使用的格式,我們首先刪除$()或反引號``。這給我們留下了我們需要執行的命令。

呼叫popen()創建一個管道。我們將要執行的命令傳遞給popen(),我們得到一個指向FILE流,我們將從中讀取命令的輸出。

呼叫fread()從管道讀取命令的輸出。將讀取的字符串存儲在緩沖區中。

刪除所有尾隨換行符。

關閉管道,并使用命令輸出返回緩沖區。

  算術擴展

使用算術擴展,我們可以讓外殼執行不同的算術運算,并將結果用于執行其他命令。盡管POSIX要求外殼程序僅支持帶符號的長整數算法,但許多外殼程序都支持浮點算法。

此外,盡管大多數外殼程序都不需要外殼程序來支持任何數學函數。對于簡單的shell,我們將僅支持帶符號的長整數算法,而沒有數學函數支持。

算術擴展寫為$((expression))。

要執行擴展,我們將定義arithm_expand()函數,具有以下原型:

char*arithm_expand(char*expr);

的arithm_expand()函數接收包含算術表達式的字符串,執行必要的計算,然后以malloc'd字符串的形式返回結果。該函數及其相關的幫助器函數既復雜又冗長,但這是主要亮點:

算術表達式轉換為反向波蘭表示法,更易于分析和計算。RPN由一系列算術運算組成,其中運算符遵循其操作數。例如,RPNx-y是xy-,以及3+4×(2?1)是3421?×+。

在轉換過程中,算術運算符被壓入一個運算符堆棧,我們將從中彈出每個運算符并稍后執行其運算。同樣,操作數被添加到它們自己的堆棧中。

一次將操作員從堆棧中彈出,然后對操作員進行檢查。根據運算符的類型,一個或兩個操作數從堆棧中彈出。控制此過程的規則是調車場算法的規則,您可以在此處閱讀。

結果將轉換為字符串,然后將其返回給調用方。

  場分裂

在字段拆分期間,shell會獲取參數擴展,命令替換和算術擴展的結果,并將它們拆分為一個或多個部分,我們將其稱為字段。該過程取決于$IFS外殼變量。IFS是一個歷史術語,代表內部字段分隔符,它起源于Unixshell沒有內置數組類型的時間。

作為解決方法,早期的Unixshell必須找到另一種表示多成員數組的方式。外殼程序將以單個字符串將數組成員連接在一起,并以空格分隔。當外殼程序需要檢索數組成員時,它將字符串分成一個或多個字段。的$IFS變量告訴外殼程序在何處確切地中斷該字符串。

  外殼解釋$IFS如下字符:

如果值$IFS是空格,制表符和換行符,或者如果未設置變量,則在輸入的開頭或結尾處的空格,制表符或換行符的任何序列都將被忽略,并且輸入中這些字符的任何序列應定界領域。

如果值$IFS為null,不得執行任何字段拆分。

否則,應依次適用以下規則:(a)$IFS在輸入的開頭和結尾應忽略空格。(b)輸入中的每次出現$IFS不是的字符$IFS空白,以及任何相鄰的空白$IFS如前所述,空白應界定一個字段。(c)非零長度$IFS空白應界定一個字段。

要執行擴展,我們將定義field_split()函數,具有以下原型:

structword_s*field_split(char*str);

路徑名擴展

在擴展路徑名期間,shell將以給定的模式匹配一個或多個文件名。除特殊字符外,該模式還可以包含普通字符*,?和[],也稱為“通配符”。

星號*匹配任意長度的字符,匹配一個字符,并且方括號引入正則表達式(RE)括號表達式。擴展的結果是名稱與模式匹配的文件列表。

要執行擴展,我們將定義pathnames_expand()函數,具有以下原型:

structword_s*pathnames_expand(structword_s*words);

此函數接受一個參數:指向我們要路徑名擴展的單詞的鏈接列表中第一個單詞的指針。對于每個單詞,該函數執行以下操作:

檢查單詞是否包含任何通配符*,?和[],通過調用輔助函數has_glob_chars(),我們將在源文件中定義pattern.c。如果單詞包含通配符,我們將其視為需要匹配的模式;否則,我們移至下一個單詞。

獲取名稱與模式匹配的文件列表,不包括特殊名稱.和..。我們將模式匹配委托給另一個幫助函數,get_filename_matches(),我們將在同一源文件中定義pattern.c。

將匹配的文件名添加到最終列表。

移至下一個單詞并循環。

返回與所有給定單詞匹配的文件名列表。

  刪除報價

單詞擴展過程的最后一步是刪除引號。引用用于刪除某些字符到shell的特殊含義。外殼會以特殊方式處理某些字符,例如反斜杠和引號。要禁止這種行為,我們需要引用那些字符以強制外殼將它們視為普通字符。

我們可以使用以下三種方式之一對字符進行引用:使用反斜杠,單引號或雙引號。反斜杠字符用于保留反斜杠后面的字符的字面意思。這類似于我們用C語言轉義字符的方式。

單引號保留引號內所有字符的字面含義,即外殼程序不嘗試對單引號字符串進行單詞擴展。

雙引號與單引號類似,不同之處在于外殼可以識別反引號,反斜杠和$標志。也就是說,外殼程序可以在雙引號字符串內執行單詞擴展。

要執行報價刪除,我們將定義remove_quotes()函數,具有以下原型:

voidremove_quotes(structword_s*wordlist)。

  放在一起

現在我們有了詞擴展功能,是時候將其結合在一起了。在本節中,我們將編寫主要的單詞擴展功能,我們將調用該功能來執行單詞擴展。反過來,此函數將調用其他函數來執行單詞擴展的各個步驟。

我們的主要功能是word_expand(),我們將在源文件中定義wordexp.c:

structword_s*word_expand(char*orig_word);

這是為了對作為唯一參數傳遞的單詞執行單詞擴展的功能:

創建原始單詞的副本。我們將在此副本上執行單詞擴展,以便在出現任何問題時將原始單詞保留完整。逐字掃描單詞,尋找特殊字符~,",',`,=,和$。如果找到上述字符之一,請致電substitute_word(),這將調用相應的單詞擴展功能。

跳過任何沒有特殊含義的字符。完成單詞擴展后,通過調用執行字段拆分field_split()。通過調用執行路徑名擴展pathnames_expand()。通過調用執行報價刪除remove_quotes()。返回擴展單詞的列表。

更新掃描儀

在本教程的第二部分中,我們編寫了tokenize()函數,我們用來獲取輸入令牌。到目前為止,我們的tokenize()函數不知道如何處理帶引號的字符串和轉義字符。要添加此功能,我們需要更新代碼。打開scanner.c文件,并將以下代碼添加到tokenize()功能之后switch聲明的開頭括號:

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;

現在,我們的詞法掃描器知道如何識別和跳過帶引號的字符串,轉義字符和其他單詞擴展構造。在此鏈接中查看更新的詞法掃描程序代碼。

  更新執行器最后,我們需要更新后端執行程序,以便可以:

在執行命令之前,對命令的參數執行單詞擴展。每個命令支持超過255個參數。打開executor.c文件,導航到do_simple_command()函數并找到以下幾行:

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;

}

使用此代碼,執行程序調用word_expand()在每個命令自變量上,并將擴展的單詞添加到自變量列表,我們最終將其傳遞給命令。該列表可以根據需要增長,這要歸功于我們check_buffer_bounds()函數,根據需要將內存分配給緩沖區。

現在剩下的就是在執行完命令后釋放參數列表,然后返回調用者。為此我們致電:

free_buffer(argc,argv);

在三個不同的位置:執行內置實用程序后,如果fork()返回錯誤狀態,然后waitpid()已經回來了。在此鏈接中查看更新的執行程序代碼。

  編譯外殼

讓我們編譯一下shell。打開您喜歡的終端模擬器,導航到源目錄,并確保其中有19個文件和2個子目錄:

現在,使用以下命令編譯外殼程序:

  make

如果一切順利gcc不應輸出任何內容,并且應該有一個名為shell在當前目錄中:

現在通過運行來調用shell./shell,然后嘗試使用我們的單詞擴展功能并檢查結果:$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

就是今天。我們的外殼現在可以處理各種單詞擴展。玩弄外殼,看看從不同類型的單詞擴展中可以得到什么結果。將結果與從默認Shell獲得的結果進行比較。

在此,我們已經取得了長足的進步,提供了許多代碼,其中大多數代碼沒有時間或空間來詳細研究。您可能需要花一些時間閱讀我們存儲庫中的代碼,以使自己熟悉單詞擴展過程。想了解更多關于Linux Shell 的信息,請繼續關注中培偉業。

標簽: IT運維
主站蜘蛛池模板: 综合在线国产 | 调教凌虐妻妾奴在线播放 | 久久综合九色综合欧美就去吻 | 国产精品免费无遮挡无码永久视频 | 国产肥熟女视频一区二区三区 | WWW片香蕉内射在袋88AV8 | 亚洲蜜臀av乱码久久精品蜜桃 | 国产偷亚洲偷欧美偷精品 | 伦理精品一区二区三精品 | 色天天综合网 | 国产又粗又黄又爽又硬的软件 | 一个人看的www免费观看视频 | 台湾妹中文娱乐网 | 精品国产VA久久久久久久冰 | 伊人精品一区二区三区 | 伊人wwwyiren22 | 国产精品成人品 | 午夜社区| 偷拍视频免费看 | 国产成人综合欧美精品久久 | 98版还珠格格免费观看 | 精品无码一区二区三区在线 | 精品无码久久久久国产 | 欧美一区二区视频高清专区 | 99久久国产露脸精品竹菊传媒 | 国内精品伊人久久久久AV | 国产成人人综合亚洲欧美丁香花 | 久久久伦理片 | 中文精品一区二区三区四区 | 久久精品aaaaaa毛片 | 久热精品免费视频 | 日韩亚洲国产免费 | 人禽另类ZZZXXXCCC | 热re99久久精品国产99热黄 | 国产免费看av大片的网站吃奶 | 一区二区三区免费观看 | 欧美一级淫片丝袜脚交 | 2018国产精品视频麻 | 精品国产yw在线观看 | 相泽南av日韩在线 | 1区2区3区在线观看 日韩综合一区二区 |