1. 程式人生 > >bash中命令前設置子進程變量的綠色方法

bash中命令前設置子進程變量的綠色方法

subst 存在 state esp 判斷 意義 pen tar font

一、語法
這其實是一個比較小的細節問題,但是覺得比較有創意(而且一用就會讓人產生“當時我就震驚鳥”了感覺),而且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 子進程退出之後父進程環境變量依然是純潔的。
二、最基礎兩個骨架函數和功能說明
對於整個bash的代碼,主入口位於parse_and_execute函數中,它的作用和函數名稱還是比較一致的,就是一個是解析,另一個是執行;函數內對應兩個實現,一個是parse_command,一個是execute_command_internal。這兩個函數籠統的說(代碼沒看那麽深,自己YY的):
1、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這個枚舉的所有類型。
2、execute_command_internal
它是真正的執行命令,這個執行包括了最為各種變量展開和替換,當然包括之後的執行。在該函數中,有一個很大的switch,而switch的條件就是這裏說的enum command_type,而這個就是parse.y中適當的時候賦值進來的,這就是語法/語義分析的功勞。這裏命令的執行還是有很多冗長的操作的,要看懂代碼,首先要知道bash的各個功能。其實代碼實現本身並不重要,例如我感覺bash的代碼就很亂,但是重要的是功能或者說是需求,一個工具是否能夠提供你所需要的(或者你一下子沒想到但是之後會用到)功能,並且讓你覺得非常好用,這就是一款產品的意義,例如瑞士軍刀,例如iphone。
三、命令前變量賦值語法分析
這個毫無疑問是在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))
四、變量展開
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 */
}
五、環境變量的設置
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使用的變量使用的是兩個獨立的地址空間。
六、環境變量的傳遞
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));

bash中命令前設置子進程變量的綠色方法