AOS ch3 bash

Table of Content:
  1. 3.1 介绍
  2. 3.2 基础语法单元
  3. 3.3 输入处理
  4. 3.4 解析
  5. 3.5 词展开
    1. 3.5.1 参数和变量展开
    2. 3.5.2 更多
    3. 3.5.3 词分割
    4. 3.5.4 globbing
    5. 3.5.5 实现
  6. 3.6 命令执行
    1. 3.6.1 重定向
    2. 3.6.2 内置命令
    3. 3.6.3 简单命令执行
    4. 3.6.4 任务控制

这个是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没有直接使用的.
并且要按照修改移动他们.这需要很多类似书签,执行时关闭的标志等等.

3.6.2 内置命令

3.6.3 简单命令执行

3.6.4 任务控制