1、語法
這實際上是一個比較小的細節問題,可是以爲比較有創意(並且一用就會讓人產生「當時我就震驚鳥」了感受),並且bash的這個功能的實現代碼爲bash代碼的晦澀性也作了很多貢獻,因此這裏仍是看一下這個比較有創意的語法。這個功能和管道同樣,感受是一個綠色環保的命令,說它綠色,就是它"事了拂衣去,深藏身與名」。這裏先從你們耳熟能詳的管道提及,它的優勢就是兩個進程共享一個文件,可是這個文件只在內存中存在,不會在系統中留下文件,當管道兩端(或者一端)結束以後,系統是乾淨的。若是使用實體文件,首先是同步性沒有保證,而後就是污染系統有殘留文件。有人說,管道是UNIX最重要的發明,它把不一樣的進程粘合在一塊兒,從而可讓每一個程序只專一本身的功能,並把這個功能作好。
而bash的語法中支持一種命令前設置子進程環境變量的方法,固然這並不神奇,神奇的是它影響且隻影響子進程的環境變量,而對父進程沒有影響,因此說它是一種綠色設置環境變量的方法。我第一次見到它是在執行一個工程的configure配置的時候使用的,由於要設置一些特殊的PATH,可是又不影響父進程的環境變量,因此使用的命令是
PATH=SomeSpecialPath:$PATH ./configure CFLAGS=-g
你們能夠看到,其中這個PATH和接下來要執行的命令之間沒有分號,可是子進程中會看到這個變量。下面是最爲原始的Demo程序
[root@Harry GreenVar]# cat GreenVar.sh
#!/bin/sh
echo ME is $ME
[root@Harry GreenVar]# echo $ME 執行腳本以前,父進程環境變量中沒有定義ME
[root@Harry GreenVar]# ME=tsecer . GreenVar.sh 在執行子進程以前設置ME變量的值爲tsecer,而後子進程中能夠看到該變量
ME is tsecer
[root@Harry GreenVar]# echo $ME 子進程退出以後父進程環境變量依然是純潔的。
2、最基礎兩個骨架函數和功能說明
對於整個bash的代碼,主入口位於parse_and_execute函數中,它的做用和函數名稱仍是比較一致的,就是一個是解析,另外一個是執行;函數內對應兩個實現,一個是parse_command,一個是execute_command_internal。這兩個函數籠統的說(代碼沒看那麼深,本身YY的):
一、parse_command
負責語法分析,並進行「斷句」(或者說將句子切割爲不一樣的WORD,要考慮到引號,括弧等),可是不負責展開。例如ME=$WHO這樣的內容是做爲一個WORD來返回的,包括其中的取值操做符$都會被原封不動的保存;可是它能夠識別出語義,例如for do之類的句式和語義。在parse_command中分析出來的語法放在全局變量global_command中,而後在parse_and_execute函數中經過else if (command = global_command)將這個變量放在command中,從而供execute_command_internal來執行這個命令其中變量。在 struct command 結構的enum command_type type;成員中說明了語法分析出來的是一個什麼句式,你們能夠看一下command_type這個枚舉的全部類型。
二、execute_command_internal
它是真正的執行命令,這個執行包括了最爲各類變量展開和替換,固然包括以後的執行。在該函數中,有一個很大的switch,而switch的條件就是這裏說的enum command_type,而這個就是parse.y中適當的時候賦值進來的,這就是語法/語義分析的功勞。這裏命令的執行仍是有不少冗長的操做的,要看懂代碼,首先要知道bash的各個功能。其實代碼實現自己並不重要,例如我感受bash的代碼就很亂,可是重要的是功能或者說是需求,一個工具是否可以提供你所須要的(或者你一會兒沒想到可是以後會用到)功能,而且讓你以爲很是好用,這就是一款產品的意義,例如瑞士軍刀,例如iphone。
3、命令前變量賦值語法分析
這個毫無疑問是在read_token_word中進行的,可是這裏還要考慮的狀況,就是最開始寫的那個命令
PATH=SomeSpecialPath:$PATH ./configure CFLAGS=-g
這裏在真正的configure命令先後都有一個變量賦值命令,以前的確定是要設置子進程的環境變量的,可是接下來的那些明顯是給子進程configure使用的,若是bash拍馬屁拍到馬腿上,把這個東西也當作環境變量吃掉了,那用戶就會至關的驚詫莫名了。
該函數的這個功能實現代碼爲
/* A word is an assignment if it appears at the beginning of a 做者很nice的給了一個註釋,說這個就是用來處理賦值的,這個賦值須要出如今簡單
simple command, or after another assignment word. This is 命令的開始,或者在另外一個賦值的後面,也就是ME=tsecer YOU=who都是能夠接受
context-dependent, so it cannot be handled in the grammar. */的。做者說這是上下文相關的,因此不能在語法(上下文無關)中處理。
if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
{
the_word->flags |= W_ASSIGNMENT;
/* Don't perform word splitting on assignment statements. */
if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)若是說能夠被接受爲賦值指令,
the_word->flags |= W_NOSPLIT; 則添加W_NOSPLIT屬性,這個屬性和W_ASSIGNMENT
} 知足接下來的判斷,返回ASSIGNMENT_WORD,進而
…… 使 command_token_position函數知足。
result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
? ASSIGNMENT_WORD : WORD;
其中使用到的相關宏定義
#define command_token_position(token) \
(((token) == ASSIGNMENT_WORD) || (parser_state&PST_REDIRLIST) || \
((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token)))
#define assignment_acceptable(token) \
(command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
4、變量展開
expand_word_list_internal---->>>separate_out_assignments
/* Separate out variable assignments at the start of the command.
Loop invariant: vp->next == lp
Loop postcondition:
lp = list of words left after assignment statements skipped
tlist = original list of words
*/
while (lp && (lp->word->flags & W_ASSIGNMENT))開始遍歷一個命令中全部的,具備W_ASSIGNMENT屬性的單詞,遇到一個無該屬性即結束循環
{
vp = lp;
lp = lp->next;
}
/* If lp != tlist, we have some initial assignment statements.
We make SUBST_ASSIGN_VARLIST point to the list of assignment
words and TLIST point to the remaining words. */
if (lp != tlist)
{
subst_assign_varlist = tlist; 這裏的subst_assign_varlist是全局變量,賦值以後父函數將會使用。
/* ASSERT(vp->next == lp); */
vp->next = (WORD_LIST *)NULL; /* terminate variable list */
tlist = lp; /* remainder of word list */
}
5、環境變量的設置
expand_word_list_internal函數中
if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
{
sh_wassign_func_t *assign_func;
/* If the remainder of the words expand to nothing, Posix.2 requires
that the variable and environment assignments affect the shell's
environment. */
assign_func = new_list ? assign_in_env : do_word_assignment;因爲命令開始全部賦值以後還有命令,因此使用assign_in_env。
tempenv_assign_error = 0;
for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
{
this_command_name = (char *)NULL;
assigning_in_environment = (assign_func == assign_in_env);
tint = (*assign_func) (temp_list->word);
assigning_in_environment = 0;
從assign_in_env函數能夠看到,
var = hash_lookup (name, temporary_env);
if (var == 0)
var = make_new_variable (name, temporary_env);
else
FREE (value_cell (var));
在放置的時候是添加到了temporary_env全局變量中,這個是和當前shell使用的變量使用的是兩個獨立的地址空間。
6、環境變量的傳遞
maybe_make_export_env()
if (temporary_env)
{
tcxt = new_var_context ((char *)NULL, 0);
tcxt->table = temporary_env;
tcxt->down = shell_variables;
}
else
tcxt = shell_variables;
temp_array = make_var_export_array (tcxt);
if (temp_array)
add_temp_array_to_env (temp_array, 0, 0);將temp_array添加到export_env中。
最後在execute_disk_command函數中使用了這個變量
exit (shell_execve (command, args, export_env));shell