zoukankan      html  css  js  c++  java
  • bash中命令前设置子进程变量的绿色方法

    一、语法
    这其实是一个比较小的细节问题,但是觉得比较有创意(而且一用就会让人产生“当时我就震惊鸟”了感觉),而且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));

  • 相关阅读:
    有用网站
    html5页面布局总结
    video和audio支持格式
    关于浏览器缓冲
    java常见面试题汇总
    jvm常用相关参数
    规律字符串拼接
    线程基础知识
    Kafka学习
    Redis学习
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486101.html
Copyright © 2011-2022 走看看