AOS ch3 bash
这个是arch of open source一书的简要翻译.其中的bash一章.
在linux下面工作,都必然会使用都bash.我们看下bash的维护者是怎么描述bash的.
注意:这里的翻译仅仅是简要的,非正式和不完备的.
中间还可能会夹杂我的评论,但是我会注意将评论和原文区分开来.
3.1 介绍
3.2 基础语法单元
3.3 输入处理
3.4 解析
3.5 词展开
在语法解析之后,执行之前,解析阶段产生的许多词需要进行一次或者多次的词展开.
例如$OSTYPE将被替换为”linux-gnu”.
3.5.1 参数和变量展开
变量是用户最常使用的.shell中的变量是无类型的.多数情况下都认为是string类型.
展开过程展开或者变换字符串类型到新的词或者列表.
这些展开直接作用的变量值本身.程序员可以使用这些功能得到
- 变量值的子串
- 字符串长度
- 去除从开头或者结束的特定模式的部分
- 替换字符串的部分为为新字符串
- 修改字符串中字符的大小写
comment
这里使用的bash的展开功能,不是sed之类的命令.
具体的列表这里就不列了.网上可以搜到很多.
3.5.2 更多
bash还可以进行更多类型的展开,每一种都有自己诡诈的规则.
第一种就是大括号展开.
pre{one, two, thress}post
->
preonepost pretwopost prethrespost
comment
这种技巧可以用于比如备份文件
mv filename{,bak}
还可以进行命令替换.这巧妙结合了shell执行命令和处理变量的能力.
shell执行命令,搜集输出,使用输出作为变量的展开值.
comment
ret=$(func par1)
这里指就是这种风格的返回值.
命令替换的一个问题就是它立刻执行封闭的命令,并且等待它完成.这样shell就
无法向其发送输入.
bash使用了一直叫做进程替换的特性来解决这个问题.
组合使用一系列的命令替换和shell pipeline,来弥补这些不足.
和命令替换类似,bash执行命令,但是让它运行后台,而不是等待它结束.
关键是bash打开一个pipe,用于这个命令的读取或者写入,并且暴露为文件名.也就是展开的结果.
comment
这里使用的是类似于mkfifo的技巧,但是实践没有利用过这个特性.
这里的区分是函数的同步/异步调用有些相似.
下一个是波浪线展开,最初用于将~alan转换成alan的home路径.
后来经过很多年,这成了一种指向许多的不同路径的方法.
comment
cd ~[TAB]
试试看.
这种方式完全是bash沉重的历史和后向兼容,以及同时作为interactive和script的用法导致的.
最后是算术展开.$((expression))可以计算按照C语言一样的规则.表达式计算的结果为展开的结果.
使用双引号和单引号在变量展开中有明显的区别.
单引号屏蔽了一切的展开,引号中的字符串不会进行任何展开,还是原始的样子.
双引号,只是执行了一些展开,屏蔽了其他的.
词展开,命令,进程,算术替换这些都执行.大括号和波浪线展开则屏蔽了.
3.5.3 词分割
词展开的结果,将会以shell变量IFS的值作为分割符进行分割.
这就是shell如何将一个词转换为多个.
一旦IFS中的任一个字母出现在结果中,bash就会将结果分割为2个.单引号和双引号屏蔽词分割.
comment
实际中双引号应该普遍使用.
因为还有一个作用,就是对空格进行屏蔽.
func $par
当par中没有空格,一切正常.
当par的值中有空格,那么func就有2个参数传递进去.这不一定是程序员想要的效果.
func "$par"
将避免这种情况.
3.5.4 globbing
split之后,shell将展开的结果并且尝试将其匹配为存在的文件名.包括任何前导文件夹路径.
3.5.5 实现
3.6 命令执行
bash pipeline的内部的命令执行阶段,就像实际发生的一样.
大多数情况下,展开的词,分解为命令的名字和一系列的参数,然后传递给操作系统.
命令的名字传递给操作系统作为要读取和执行的文件,其他的作为argv参数传递给命令.
以上的描述集中在POSIX是如何调用简单的命令的.这是最为常见的场景,但是bash还提供其他的功能.
3.6.1 重定向
shell是操作系统的界面.作为这种性质的体现之一,就是重定向从它执行的命令的输入和输出的能力.
重定向的语法揭示了shell的早期用户的诡辩,
直到最近(??什么时候),才需要用户跟踪他们使用的文件描述符,并且显式的指定数字(对于标准输入输出和错误之外的)
最近的添加了一个重定向语法.
允许用户直接选定合适的文件描述符,并且使用它赋值到特定的变量,而不是用户指定的.
这使得用户不必跟踪文件描述符,但是要进行额外的处理:shell必须在正确的地方duplicate文件描述符,确保将他们赋值到特定的变量.
comment
这种语法没有用过
这里有另外一个例子:此法分析器如何通过执行命令传递信息到解析器.
- 分析将单词分类,重定向包含的变量赋值.
- 解析器,在相应的语法生成的结果下,创建重定向对象,使用标志来说明需要进行赋值.
- 定向的代码则解释标志,确保文件描述符赋值到对应的变量.
重定向相关实现最难的地方在于记得如何取消重定向.
shell有意的模糊了从文件系统执行命令创建新进程的这种方式
和 shell内部命令
这两种方式.但是无论命令如何实现,重定向的左营不应该持续到命令结束.
因此shell要跟踪如何撤销每一个重定向,否则重定向shell内部命令将会改变shell的标志输出.
bash指导如何关闭每一种重定向,不管是关闭它开启的文件描述符,还是通过保存duplicated的文件描述,然后使用dup2恢复他们.
这些使用相同的重定向对象,另一些则通过parser创建,使用相同的函数处理.
comment
# TODO:
# OS中关于如何建立一个简单的sh的重定向功能.
因为多次重定向实现为简单的对象列表,重定向保存为分离的列表,进行撤销重定向.
当命令结束的时候,按照列表进行处理,但是shell需要小心的确保确实命令结束了,
因为重定向到一个shell的函数,或者内置的”.”命令,将会持续作用,直到函数或者内置命令结束.
当它没有发起一个命令,内置的exec触发的撤销列表会被简单的丢弃掉,因为关联到exec的重定向还要继续保持在shell环境中.
另外一个问题在于bash自己引起的.
Bourne sh的历史版本,只能允许用户处理0-9文件描述符.
保留大于10的作为shell内部使用.bash放松了这个限制,允许用户处理任何在OS限制最大文件描述符个数之内.
这意味着bash必须跟踪自己的文件描述符,包括外部库带卡的,并且shell没有直接使用的.
并且要按照修改移动他们.这需要很多类似书签,执行时关闭的标志等等.