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
2
3
4
5
6
7
8
9
10
register int i;
int code, saverst, old_errexit_flag;
volatile int locally_skip_execution;
volatile int arg_index, top_level_arg_index;
//第一个参数是 i 是用来在main函数中的for循环中加减的参数
//code用来接受函数的返回值, 一般用作进行状态的判断
//saverst用来在后面的参数解析的时候短暂保存状态
//old_errexit_flag用来保存错误退出码
//arg_index是函数的argv的索引
//top_level_arg_index用来保存arg_index

下面主要是bash需要的一些参数的定义:

1
2
3
4
5
6
7
8
9
10
11

// arg_index: 参数索引
// local_pending_command: 本地将要执行的命令
// want_pending_command: 提供-c命令
// locally_skip_execution:
// read_from_stdin: 提供-s命令
// default_input: 默认输入文件流
arg_index = 1;
local_pending_command = (char *)NULL;
want_pending_command = locally_skip_execution = read_from_stdin = 0;
default_input = stdin;

分析 main.c 函数

使用的 bash 版本是 bash-1.14.7 版本。这个版本比较古老,主要是代码相较于新本版的 bash 更加简洁,没有一层层的封装。

首先主要是对于 main.c 函数的主要流程的分析。

main.c 流程分析

这段代码是一个shell程序的主函数(main),可能是bash或其他类似shell的简化版本。下面是对这段代码的分析:

  1. 变量声明

    • register int i;:使用register关键字声明一个整型变量i,这通常用于循环或频繁使用的地方,以提高访问速度。
    • int arg_index, locally_skip_execution;:声明了两个整型变量,用于跟踪参数索引和是否跳过执行。
    • int top_level_arg_index, read_from_stdin;:声明了两个整型变量,用于跟踪顶级参数索引和是否从标准输入读取。
    • FILE *default_input;:声明了一个指向FILE结构的指针,用于默认输入。
  2. 特定系统问题的修复

    • 检查是否存在NeXT 2.1 rlogind的bug,如果是,则尝试打开/dev/tty,如果失败,则尝试使用ttyname获取终端名称并打开。
  3. 调试和用户信息

    • 如果正在调试登录shell,则无限循环。
    • 获取当前用户的UID、GID、EUID和EGID。
    • 检查是否以特权模式运行(UID或GID与EUID或EGID不匹配)。
  4. 环境变量检查

    • 检查环境变量POSIXLY_CORRECTPOSIX_PEDANTIC是否存在,以确定是否启用POSIX兼容模式。
  5. 内存检查

    • 如果定义了USE_GNU_MALLOC_LIBRARY,则调用mcheck函数进行内存检查。
  6. 异常处理

    • 使用setjmpsubshell_top_level设置异常处理。
  7. 初始化

    • 初始化参数索引、本地变量等。
    • 检查是否为登录shell,并进行相应的初始化。
  8. 解析命令行参数

    • 循环遍历命令行参数,解析长参数和短参数。
    • 根据参数设置标志,如interactive_commentslogin_shell等。
  9. 交互式shell检测

    • 根据参数和标准输入/输出是否为终端,设置是否为交互式shell。
  10. 关闭文件描述符

    • 如果是登录shell且交互式,关闭文件描述符3到19。
  11. 初始化shell

    • 调用shell_initialize函数进行shell初始化。
  12. 版本显示和启动脚本执行

    • 显示版本信息。
    • 执行启动脚本。
  13. 处理命令行参数

    • 如果有-c参数,将剩余参数绑定到$0$n
  14. 交互式shell设置

    • 如果是交互式shell,设置邮件检查、历史记录等。
  15. 读取输入并执行命令

    • 根据是否交互式,设置输入源。
    • 进入命令读取循环。
  16. 退出处理

    • 执行退出陷阱。
    • 保存历史记录。
    • 结束作业控制。
    • 返回最后命令的退出状态。

这段代码展示了一个shell程序的启动过程,包括参数解析、环境设置、异常处理、交互式模式检测、命令执行等。代码中包含了大量的注释,有助于理解每个部分的功能。

Shell命令处理

我们知道,在 shell 中有下面这些符号是需要特殊处理的:

  • |:管道符,用于将一个命令的输出作为下一个命令的输入。
  • >:重定向符,用于将命令的输出重定向到一个文件。
  • <:重定向符,用于将命令的输入重定向到一个文件。
  • &&:逻辑与,用于连接两个命令,只有前一个命令执行成功,才执行后一个命令。
  • ||:逻辑或,用于连接两个命令,只有前一个命令执行失败,才执行后一个命令。
  • ;:分号,用于分隔多个命令,并在每个命令执行完毕后,才执行下一个命令。
  • ():括号,用于将命令组成一个整体,并作为一个命令执行。
  • {}:花括号,用于将命令组成一个整体,并作为一个命令执行。
  • &:后台运行符,用于将命令放入后台运行。

bash源码分析(1)--初始化
https://ysc2.github.io/ysc2.github.io/2024/01/10/bash源码分析-1-初始化/
作者
Ysc
发布于
2024年1月10日
许可协议