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