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

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

相關文章
相關標籤/搜索