千家信息网

SHELL运行流程是怎么样的

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章给大家分享的是有关SHELL运行流程是怎么样的的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一.启动过程shell.c是shell主函数main所在文件。因此she
千家信息网最后更新 2026年11月01日SHELL运行流程是怎么样的

这篇文章给大家分享的是有关SHELL运行流程是怎么样的的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

一.启动过程

shell.c是shell主函数main所在文件。因此shell的启动可以认为从shell.c文件开始。main函数完成的主要工作流程是包括:检查启动的运行环境(是否通过sshd启动,是否运行于emacs环境下,是否运行于cgywin环境下,是否是交互式shell,是否是login shell等,对系统进行内存泄露检查,是否是受限shell),读取配置文件(顺序为/etc/profile and( ~/.bash_profile OR ~/.bash_login OR ~/.profile)前面的存在不会读后面的),设置运行需要的全局变量的值(当前环境变量、shell的名称、启动时间、输入输出文件描述符、语言本地化的相关设置),处理参数和选项(即带有-c -s --debugger等参数和选项),设置参数和选项的值(run_shopt_alist ()函数调用shopt_setopt函数设置选项的值;绑定$位置参数的值),然后根据不同的启动参数进入以下不同分支:

  1. 如果是只进行参数扩展而不执行命令,调用run_wordexp函数扩展参数,然后调用exit_shell (last_command_exit_value)函数以上次命令执行的返回值为返回值退出。

  2. 如果是以-c参数模式启动shell,分为两种情况:一:如果是附带了字符串参数作为要执行的命令,则调用run_one_command (command_execution_string)执行-c附带的命令,参数command_execution_string保存-c后面附带的字符串命令值。执行完毕后调用exit_shell (last_command_exit_value)退出。二:如果是期待用户输入要执行的命令,则跳转到分支3。

  3. 将shell_initialized置为1表示shell初始化完成。调用eval.c中定义的函数reader_loop()不断的读取和解析用户输入,如果reader_loop函数返回,则调用exit_shell、(last_command_exit_value)退出shell。

二.命令解析和执行流程

1. 主要相关文件
Eval.cCommand.hCopy_cmd.cExecute_cmd.cMake_cmd.c
2. shell命令结构:

shell中用如下结构体来表示一个命令。

typedef struct command {  enum command_type type;   /* 命令的类型 */  int flags;                /* 标记位,将影响命令的执行环境 */  int line;                 /* 命令从哪一行开始 */  REDIRECT *redirects;      /*关联的重定向操作*/  union {/*以下是一个联合value,保存具体的"命令体",可能是for循环,case条件,while循环等,union结构体的特征是只有一个值是有效的,因此以下命令种类是并列的,后面有每一种命令类型的注释*/    struct for_com *For;    struct case_com *Case;    struct while_com *While;    struct if_com *If;    struct connection *Connection;    struct simple_com *Simple;    struct function_def *Function_def;    struct group_com *Group;#if defined (SELECT_COMMAND)    struct select_com *Select;#endif#if defined (DPAREN_ARITHMETIC)    struct arith_com *Arith;#endif#if defined (COND_COMMAND)    struct cond_com *Cond;#endif#if defined (ARITH_FOR_COMMAND)    struct arith_for_com *ArithFor;#endif    struct subshell_com *Subshell;    struct coproc_com *Coproc;  } value;} COMMAND;

其中一个很关键的成员是联合union类型value,它指出了该命令的类型,也给出了保存命令具体内容的指针。从该结构的可选值来看,shell定义的命令共有for循环、case条件、while循环、函数定义、协同异步命令等14种。

其中,经过对所有命令执行路径的分析,确定类型为simple的command是经过命令替换后的最原子的命令操作,其余类型的命令都是由若干simple command构成的。

在shell启动之后,无论是进入上面的2和3两个分支中的哪一个,最后解析命令所用到的函数都是execute_cmd.c中定义的函数。分支1不涉及到命令的解析,所以不在这里分析。

3. 分支2的第一种情况:

run_one_command (command_execution_string) 执行的过程中调用parse_and_execute (在evalstring.c中定义)解析与执行命令,parse_and_execute中实际调用execute_command_internal函数进行命令的执行。

4. 分支2的第二种情况和分支3:

reader_loop函数调用read_command函数解析命令,read_command函数调用parse_command()函数进行语法分析,parse_command()调用语法分析器y.tab.c中的yyparse()(该函数由yyac自动生成,因此不再往函数内部跟进),将解析结果的命令字符串保存在全局变量GLOBAL_COMMAND中,然后执行execute_command函数(定义在execute_cmd.c中),execute_command函数再调用execute_command_internal函数进行命令的执行。至此分支2和分支3的情况又合并到execute_command_internal的执行上。

5. execute_command_internal内部流程:

该函数是shell源码中执行命令的实际操作函数。他需要对作为操作参数传入的具体命令结构的value成员进行分析,并针对不同的value类型,再调用具体类型的命令执行函数进行具体命令的解释执行工作。

具体来说:如果value是simple,则直接调用execute_simple_command函数进行执行,execute_simple_command再根据命令是内部命令或磁盘外部命令分别调用execute_builtin和execute_disk_command来执行,其中,execute_disk_command在执行外部命令的时候调用make_child函数fork子进程执行外部命令。

如果value是其他类型,则调用对应类型的函数进行分支控制。举例来说,如果是value是for_commmand,即这是一个for循环控制结构命令,则调用execute_for_command函数。在该函数中,将枚举每一个操作域中的元素,对其再次调用execute_command函数进行分析。即execute_for_command这一类函数实现的是一个命令的展开以及流程控制以及递归调用execute_command的功能。

因此,从main函数启动到命令执行的主要流程图可以表现为下图所示:

6. 从启动到命令解释的函数级流程图:

括号内为函数定义所在的文件。

三. 变量控制

1. 主要相关文件
variables.cvariables.h
2. 重要数据结构

BASH中主要通过变量上下文和变量两个结构体来描述一个变量结构。以下分别介绍。

变量上下文:上下文又可以理解为作用域,可以比照C语言中的函数作用域,全局作用域来理解。一个上下文中的变量都是在这个上下文中可见的。
变量上下文结构定义:

typedef struct var_context {  char *name;           /* name如果为空则表示它存储的是bash全局上下文,否则表示名为name的函数的局部上下文*/  int scope;         /*上下文在调用栈中的层数,0代表全局上下文 ,每深入一层函数调用scope递增1*/  int flags;  /*标志位集合flags记录该上下文是否为局部的、是否属于函数、是否属于内部命令,或者是不是临时建立的等信息*/  struct var_context *up; /* 指向函数调用栈中上一个上下文*/  struct var_context *down;   /*指向函数调用栈中下一个上下文*/  HASH_TABLE *table;    /* 同一上下文中的所有变量集合hash表,即名值对 */} VAR_CONTEXT;

描述一个变量的作用域的结构体。一个上下文中的所有变量,存放在var_context的table成员中。

变量:bash中的变量不强调类型,可以认为都是字符串。其存储结构如下

typedef struct variable {  char *name;                  /*指向变量的名 */  char *value;                  /*指向变量的值*/  char *exportstr;            /*指向一个形如"名=值"的字符串*/  sh_var_value_func_t *dynamic_value;    /* 如果是要返回一个动态值的函数,比如$SECONDS       或者$RANDOM,则函数指针指向生成该值的函数。*/  sh_var_assign_func_t *assign_func; /* 如果是特殊变量被赋值时需要调用的回调函数,则其函数指针值保存在这里*/  int attributes;         /* 只读,可见等属性*/  int context;                    /*记录该上下文变量属于可访问的作用域内局部变量栈的哪一层*/} SHELL_VAR;

由于所有变量笼统的由字符串来表示,因此提供了attributes属性成员来修饰变量的特性,比如属性可以是att_readonly表示只读,att_array表示是数组变量,att_function表示是个函数,att_integer表示是整型类变量等等。

3. 作用机理

shell程序的执行伴随着一个个上下文的切换,shell源码中的变量控制也是基于这一点。将变量绑定于一个一个的上下文中。

举例来说,一开始默认存在的是全局上下文,这里称为global,其中包含有由main函数的参数或者配置文件传入的变量值。如果这时进入了一个函数foo的执行中,则foo先从全局上下文获取要导出的变量,加上自己新增的变量,构成foo的上下文局部变量,将foo的上下文压入调用栈。这时调用栈看起来如下所示。

  • 栈顶 :foo上下文(包含foo上下文的所有局部变量)

  • 栈底:global全局上下文(包含所有全局变量)

为了解释更详细的情况,假设在foo中又调用了fun函数,则fun先从foo中获取要导出的变量,加上自己新增的变量,构成fun的上下文局部变量,然后将fun的上下文压入调用栈的栈顶

。这是调用栈看起来如下所示。

  • 栈顶 :fun上下文(包含fun上下文的所有局部变量)

  • 栈中 :foo上下文(包含foo上下文的所有局部变量)

  • 栈底:global全局上下文(包含所有全局变量)

此时假设fun函数执行完毕,则将fun上下文从栈中pop出,局部变量全部失效。调用栈又变成如下所示。

  • 栈顶 :foo上下文(包含foo上下文的所有局部变量)

  • 栈底:global全局上下文(包含所有全局变量)

变量的查找顺序:从栈顶往栈底,即如果栈顶上下文中没有要查找的变量,则查找其在栈中的下一个上下文,如果整个调用栈查找完毕也没有找到,则查找失败。举例来说,如果在栈顶上下文中有PWD变量(当前工作路径),就不会去查找全局的PWD变量,这保证了局部变量覆盖的正确语义。

4. 特殊变量:

bash中定义了若干特殊变量,特殊变量的意思是在该变量被修改后需要做一些额外的连贯工作。比如表示时区的变量TZ被修改了之后需要调用tzset函数修改系统中相应的时区设置。bash给这一类变量提供了一个回调函数接口,供其值发生改变的情况下来调用该回调函数。这可以类比数据库中的触发器机制。在bash中,特殊变量保存在一个全局数组special_vars中。其定义如下:

struct name_and_function {            char *name;/*变量名*/            sh_sv_func_t *function;/*变量值修改时要触发的回调函数的函数指针*/};

该结构表示一个特殊变量结构,用于生成specialvars数组。回调函数一般是sv变量名的命名方式。

static struct name_and_function special_vars[] = {  { "BASH_XTRACEFD", sv_xtracefd },#if defined (READLINE)#  if defined (STRICT_POSIX)  { "COLUMNS", sv_winsize },#  endif  { "COMP_WORDBREAKS", sv_comp_wordbreaks },#endif  { "FUNCNEST", sv_funcnest },  { "GLOBIGNORE", sv_globignore },#if defined (HISTORY)  { "HISTCONTROL", sv_history_control },  { "HISTFILESIZE", sv_histsize },  { "HISTIGNORE", sv_histignore },  { "HISTSIZE", sv_histsize },  { "HISTTIMEFORMAT", sv_histtimefmt },#endif#if defined (__CYGWIN__)  { "HOME", sv_home },#endif#if defined (READLINE)  { "HOSTFILE", sv_hostfile },#endif  { "IFS", sv_ifs },  { "IGNOREEOF", sv_ignoreeof },  { "LANG", sv_locale },  { "LC_ALL", sv_locale },  { "LC_COLLATE", sv_locale },  { "LC_CTYPE", sv_locale },  { "LC_MESSAGES", sv_locale },  { "LC_NUMERIC", sv_locale },  { "LC_TIME", sv_locale },#if defined (READLINE) && defined (STRICT_POSIX)  { "LINES", sv_winsize },#endif  { "MAIL", sv_mail },  { "MAILCHECK", sv_mail },  { "MAILPATH", sv_mail },  { "OPTERR", sv_opterr },  { "OPTIND", sv_optind },  { "PATH", sv_path },  { "POSIXLY_CORRECT", sv_strict_posix },#if defined (READLINE)  { "TERM", sv_terminal },  { "TERMCAP", sv_terminal },  { "TERMINFO", sv_terminal },#endif /* READLINE */  { "TEXTDOMAIN", sv_locale },  { "TEXTDOMAINDIR", sv_locale },#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)  { "TZ", sv_tz },#endif#if defined (HISTORY) && defined (BANG_HISTORY)  { "histchars", sv_histchars },#endif /* HISTORY && BANG_HISTORY */  { "ignoreeof", sv_ignoreeof },  { (char *)0, (sh_sv_func_t *)0 }};

感谢各位的阅读!关于"SHELL运行流程是怎么样的"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

函数 变量 命令 上下 上下文 全局 结构 参数 局部 类型 分支 流程 文件 运行 特殊 作用 字符 字符串 情况 指向 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 云南咖啡 广州为想互联网科技有限公司 互联网金融监管科技数据 互联网公司 科技公司 互联网金融公司科技 互联网女皇 腾讯科技 四个字互联网科技公司取名 须眉科技是互联网公司吗 中国科技互联网公司收入 宁夏科技产业互联网案例 港股科技和互联网 互联网科技产业特征 速维有品互联网信息科技(苏州)有限公司 中国互联网十大科技股排名 浙南科技城互联网大厦建设工程 烟台灵鸽互联网科技有限公司 山东子淼互联网科技有限公司 福州德业互联网科技 全球科技与互联网公司市值变化 云南互联网科技学费 互联网科技活动banner 金融科技 互联网金融 互联网科技集团 左右互联网络科技 中盈科技互联网信息有限公司 广州慧亚互联网科技怎么样 晋城互联网科技 特约风豹互联网科技为什么扣费 广州百诚互联网科技有限公司官网 版纳互联网科技选哪家
0