bash源码分析(1)--初始化
主要是bash的main函数中的内容
缘起
马上毕业了, 想写一个类似于bash
这样的shell
.作为自己的一个项目. 但是又苦于没有现成的书籍和资料. 只好自己研究一下bash
的源代码, 看看前辈是怎么写的. 我选择的bash版本是bash-2.04
. 随便选的, 主要是太新的bash
代码抽象度都很高,之后找到之前的版本来看看.
初始化
我首先看的就是shell.c
这个文件, 其中包含了bash
的初始化操作, 也就是main.c
函数. 下面总结一下bash
是如何初始化的, 它的初始化的顺序是怎么样的, 代码架构是怎样的.
由于main
函数很长, 所以我将其进行划分描述.
第一部分 杂项设置
这一部分是main
函数的开头, 主要进行了一些需要使用的参数的定义,并且初始化、检查终端(tty)、根据当地的地理地址设置好相关内容、设置好setjmp()
函数方便之后重新初始化的时候使用函数longjmp()
跳回来.
下面根据代码来详细讲讲每一部分
参数定义&初始化
main
函数开头就定义了几个变量, 但是这些变量是给main
函数使用的, 而不是bash
的参数. 如:
1 |
|
下面主要是bash
需要的一些参数的定义:
1 |
|
分析 main.c 函数
使用的 bash 版本是 bash-1.14.7 版本。这个版本比较古老,主要是代码相较于新本版的 bash 更加简洁,没有一层层的封装。
首先主要是对于 main.c 函数的主要流程的分析。
main.c 流程分析
这段代码是一个shell程序的主函数(main
),可能是bash或其他类似shell的简化版本。下面是对这段代码的分析:
变量声明:
register int i;
:使用register
关键字声明一个整型变量i
,这通常用于循环或频繁使用的地方,以提高访问速度。int arg_index, locally_skip_execution;
:声明了两个整型变量,用于跟踪参数索引和是否跳过执行。int top_level_arg_index, read_from_stdin;
:声明了两个整型变量,用于跟踪顶级参数索引和是否从标准输入读取。FILE *default_input;
:声明了一个指向FILE
结构的指针,用于默认输入。
特定系统问题的修复:
- 检查是否存在NeXT 2.1 rlogind的bug,如果是,则尝试打开
/dev/tty
,如果失败,则尝试使用ttyname
获取终端名称并打开。
- 检查是否存在NeXT 2.1 rlogind的bug,如果是,则尝试打开
调试和用户信息:
- 如果正在调试登录shell,则无限循环。
- 获取当前用户的UID、GID、EUID和EGID。
- 检查是否以特权模式运行(UID或GID与EUID或EGID不匹配)。
环境变量检查:
- 检查环境变量
POSIXLY_CORRECT
或POSIX_PEDANTIC
是否存在,以确定是否启用POSIX兼容模式。
- 检查环境变量
内存检查:
- 如果定义了
USE_GNU_MALLOC_LIBRARY
,则调用mcheck
函数进行内存检查。
- 如果定义了
异常处理:
- 使用
setjmp
和subshell_top_level
设置异常处理。
- 使用
初始化:
- 初始化参数索引、本地变量等。
- 检查是否为登录shell,并进行相应的初始化。
解析命令行参数:
- 循环遍历命令行参数,解析长参数和短参数。
- 根据参数设置标志,如
interactive_comments
、login_shell
等。
交互式shell检测:
- 根据参数和标准输入/输出是否为终端,设置是否为交互式shell。
关闭文件描述符:
- 如果是登录shell且交互式,关闭文件描述符3到19。
初始化shell:
- 调用
shell_initialize
函数进行shell初始化。
- 调用
版本显示和启动脚本执行:
- 显示版本信息。
- 执行启动脚本。
处理命令行参数:
- 如果有
-c
参数,将剩余参数绑定到$0
到$n
。
- 如果有
交互式shell设置:
- 如果是交互式shell,设置邮件检查、历史记录等。
读取输入并执行命令:
- 根据是否交互式,设置输入源。
- 进入命令读取循环。
退出处理:
- 执行退出陷阱。
- 保存历史记录。
- 结束作业控制。
- 返回最后命令的退出状态。
这段代码展示了一个shell程序的启动过程,包括参数解析、环境设置、异常处理、交互式模式检测、命令执行等。代码中包含了大量的注释,有助于理解每个部分的功能。
Shell命令处理
我们知道,在 shell 中有下面这些符号是需要特殊处理的:
|
:管道符,用于将一个命令的输出作为下一个命令的输入。>
:重定向符,用于将命令的输出重定向到一个文件。<
:重定向符,用于将命令的输入重定向到一个文件。&&
:逻辑与,用于连接两个命令,只有前一个命令执行成功,才执行后一个命令。||
:逻辑或,用于连接两个命令,只有前一个命令执行失败,才执行后一个命令。;
:分号,用于分隔多个命令,并在每个命令执行完毕后,才执行下一个命令。()
:括号,用于将命令组成一个整体,并作为一个命令执行。{}
:花括号,用于将命令组成一个整体,并作为一个命令执行。&
:后台运行符,用于将命令放入后台运行。