linux中bash源码,【原创】Bash源码分析(一)

作者简介:15年通讯底层研发经验,熟悉linux/vxworks等实时操作系统的内核原理和实现,在虚拟化的openstack,kubernetes,docker等领域也初有涉猎。

摘要:本文讲述当下留下的linux的bash的源代码,通过代码分析和单步调试解析bash的运行流程,适合喜欢研究linux原理的高级用户。分析的源代码来自gnu的开源项目https://git.savannah.gnu.org/git/bash.git,例子是作者自己编写,可以随意引用。

1 引言

Bash这个程序作为一个linux的用户,用的实在太频繁了,但一般局限于会用就结束了,一直没机会研究bash本身的原理。因工作需要,调试一个bash的cpu冲高问题,趁此机会对bash的源码做了一些研究,希望能对大家有点帮助。

2 linux的各种主流shell介绍

现在一般使用的shell有sh,bash和csh这几种,我们这里主要说的是bash,其他shell的源代码逻辑也差不多。

3 bash使用到的主要数据结构介绍

3.1 COMMAND

4debf261ab88

COMMAND是所有数据结构的纲,从这里可以看出一个bash实际能执行的语句有14种,分别位for,case,while,fi,connection,simple_com,function,group,select,arith,cond,arith_for,subshell,coproc,其中select,arith,cond,arith_for这4个命令需要打开对应的编译开关之后才能执行。 除了下面的这个union外,另外几个属性分别对应命令类型,行号和执行环境控制参数。其中控制参数有很多,每个控制参数占用一个bit位,包括是否启动子shell,是否忽略exit值等。

4debf261ab88

这些flag可以在bash启动shell脚本时设置,或者在shell脚本内部调用set指令来设置,一般用户不怎么关注,高阶用户可以看看:

4debf261ab88

3.2 FOR_COM

4debf261ab88

FOR_COM对应的shell语句是for name in map_list; do action; done 从结构体定义可以看出,除了和COMMAND相同的flags和行号外,for语句是有一个变量名,一个列表和一个递归的COMMAND组成的,实际for循环执行过程中也是将列表中的每个元素拿出来赋值给变量名,并执行action中的脚本段。 从这里的flags,可以看出,每条命令的flags是可以单独设置的,本条命令设置的控制参数可以不影响其他命令的控制参数。

3.3 CASE_COM

4debf261ab88

对照下面的脚本,可以看出,先判断一个变量,变量判断晚走到复合语句clauses,注意clause最终实现的时候是一个单向链表,链表中每个元素由一个样式的列表和一个执行体action来组成。

4debf261ab88

3.4 WHILE_COM

4debf261ab88

WHILE_COM比较简单,主体有两部分组成,判断条件和执行体。上篇文章调试过程中导致CPU冲高到100%的例子就是用的WHILE_COM,不过例子中的WHILE_COM的判断条件留空,相当于永远为true。

4debf261ab88

3.5 IF_COM

4debf261ab88

IF_COM由3段组成,条件判断,条件为true时的执行体和条件为false时的执行体,其中false情况下的执行体可以为空。

4debf261ab88

从IF_COM的定义看,3部分都可以是复杂的COMMAND结构,所以嵌套起来也可以做的非常复杂,例如可以在test部分通过执行脚本,依靠脚本的返回值来判断是应该执行true_case还是false_case。

3.6 CONNECTION

4debf261ab88

CONNECTION由4个属性组成:ignore字段对应其他命令结构种的flags,但对连接命令实际上没有用first对应第一条命令,second对应第二条命令,connector对应两条命令之间的连接符。难道只能两个命令一起用,不能多余两个命令一起调用?显然不是,一个CONNECTION对应一个连接符连接起来的两段命令,每段命令又可以是一个CONNECTION,这样就形成了级联的效果。 CONNECTION有3种:AND_AND对应“&&”,表示first执行返回结果为0的时候执行second;OR_OR对应“||”,表示first执行返回结果为非0的时候执行second;分号对应的connector还是分号,表示无论first执行结果是0还是非0,都执行second。是不是有点像C语言里面的&&,||和;?

4debf261ab88

上面的三个例子别真的执行,后果很严重(:))。第一条表示删除$dir对应值的目录中的所有文件;第二条表示$dir不存在的时候删除当前目录下面的所有文件;第三条表示,如果$dir存在就删除$dir目录下的所有文件,如果不存在就删除当前目录下面的所有文件。

3.7 SIMPLE_COM

4debf261ab88

SIMPLE_COM按字面意思就是简单命令,结构体由四部分组成,通用的flags,行号line,命令队列WORD_LIST,重定向队列REDIRECT。 WORD_LIST队列比较容易理解。一堆命令的集合:

4debf261ab88

其中单个命令还可以独立设置flags:

4debf261ab88

REDIRECT就是重定向的意思,这里也有很多种重定向,结构体的各个属性的含义:next,组成重定向链表的指针;redirector,重定向的源;rflags,重定向时使用的私有flags;flags,打开重定向目标文件时的flags;instruction,重定向的实际功能指令,这个又有很多种,下面会详细描述;redirectee,重定向的目的文件描述符或者文件名;here_doc_eof,本地文件。

4debf261ab88

从REDIRECTEE的定义看,它既可以是一个文件描述符,例如0表示标准输入,1表示标准输出,2表示错误输出,也可以是一个文件名。

4debf261ab88

Bash支持二十种不同的重定向,后面会根据bash的源代码来一一解释一下具体内容(bash源代码的注释对重定向的含义理解也有很多帮助):

4debf261ab88

先来五种输出的重定向:普通输出,强制输出,错误和标准输出,叠加输出,错误和标准叠加输出。统一说一下几个概念,标准输出就是2级stdout,错误输出就是3级sdterr,强制的意思是文件存在的情况下会被先清空,再增加,叠加输出的意思是原有内容后面再增加。

4debf261ab88

再来九种输入和输出重定向:普通输入重定向,后台执行,输入和输出同时重定向,去掉空格的输入重定向,输入重定向,字符串作为输入,关闭重定向源(怎么还有这种应用场景?),复制输入,复制输出。 紧接着六种输入输出的重定向分别为输入剪切,输出剪切,字符指向的输入剪切和字符指向的输出剪切,字符指向的输入复制和字符指向的输出复制。

Bash的重定向真是博大精深!!

3.8 FUNCTION_DEF

4debf261ab88

FUNCTION_DEF由五部分组成,通用flags,起始行号,函数名,解析之后的函数执行体,如果函数定义在文件中,最后会有文件名。函数的定义也可以有入参,入参的提取和文件执行时类似的,都是走$1,$2类似的形式获得的。

4debf261ab88

3.9 GROUP_COM

4debf261ab88

GROUP_COM是个什么鬼?通过分析group的处理函数,发现group原来就是多个命令组成的命令段,一般用{}包围起来,从group_command_nesting变量的变化看,group是支持多层嵌套的。

4debf261ab88

3.10 SELECT_COM

4debf261ab88

SELECT_COM并不是每个版本的bash都存在,可以通过在bash里面敲help来确定其是否存在。下面这个bash 4.2.46的版本中是打开了select的开关的:

4debf261ab88

SELECT_COM的作用是为了生成一个简单的菜单,用户通过选择菜单来让系统执行对应的命令,常见的SELECT_COM是时区配置时使用的。

4debf261ab88

具体分析/usr/bin/tzselect源码时发现,为了做到各个shell之间的兼容,这个脚本写的比想象中要复杂的多。 首先要是一下版本号的记录:

4debf261ab88

跳过紧接着的注释,然后是版本兼容性判断,使用帮助:

4debf261ab88

然后很无聊的定义了一个完全不用的变量IFS,但用来定义IFS的newline后面倒是用过。 为了规避bug,还要把PS3清空。

4debf261ab88

终于进正题了,先选择大洲或者大洋:

4debf261ab88

根据大洲或者大洋,通过awk汇总对应的国家列表:

4debf261ab88

二级select,选择国家:

4debf261ab88

再次祭出awk,通过国家汇总时区列表:

4debf261ab88

第三重select,选择时区:

4debf261ab88

计算好时区之后,出现第四重select,确认是否要修改:

4debf261ab88

还要判断一下当前是cshell还是其他shell,指导用户将当前的时区改到shell的启动脚本里面去(老大你写了这么多代码,不能自动把这句加进去么?还是要手动加:))。

4debf261ab88

3.11 ARITH_COM

4debf261ab88

ARITH_COM也是需要开关打开的,不过当前默认用的bash都是支持的,这个命令的意思是算术表达式,算术表达式要用(())包起来,要不然bash会不知道你想当做算术表达式使用,如果执行“echo 1+1”会怎么样?bash认为它是一个文本,直接将文本本身显示出来了。

4debf261ab88

加上(())之后echo还是失败的:

4debf261ab88

再加一个$之后终于正常了:

4debf261ab88

实际操作的时候,发现[]包起来的算术表达式也能用,但不加$的时候不会报错,加了会触发求值:

4debf261ab88

通过查看代码,发现,只有(())是算术表达式:

4debf261ab88

而[]只是为了兼容posix.2d9的一种算术替换规则,也就是说$[1+1]是直接替换成了2,而(())还需要走到算术表达式求值过程(是不是有点饶)。

4debf261ab88

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Bash是一个在Linux系统广泛使用的命令行解释器和脚本语言。它的源码可以通过GNU的开源项目https://git.savannah.gnu.org/git/bash.git获取。\[2\]如果你对Linux原理感兴趣,你可以通过分析和单步调试Bash源码来了解它的运行流程。\[2\]这个源码分析的例子是作者自己编写的,你可以随意引用。\[2\]在Linux系统Bash的配置文件通常是/etc/bashrc和~/.bashrc。\[3\]其,/etc/bashrc是CentOS(或Red Hat系统)特有的,而其他不同的Linux发行版可能会将配置文件放置在不同的位置。\[3\]此外,~/.bashrc文件会调用/etc/bashrc和/etc/profile.d/*.sh文件。\[3\] #### 引用[.reference_title] - *1* *2* [【原创Bash源码分析(一)](https://blog.csdn.net/weixin_33162074/article/details/116859151)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Linux基础知识与实操-篇四:初识bash与配置](https://blog.csdn.net/qq_45966201/article/details/127777990)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值