在上一篇博客中,已经介绍了些许系统管理员和程序员用到的一些高级bash指令,那么这篇我们将介绍shell进程的技巧。

shell类型

一般来说,系统启动什么样的shell进程取决于个人的ID设置,在/etc/passwd文件中我们可以看到ID的记录,只要登录,默认的虚拟shell程序就会运行。

在现在使用的ubuntu版本上,默认的系统shell和默认的交互shell并不一致,默认的交互shell是/bin/bash,但是作为系统的默认shell却是/bin/sh被设置成dash shell。

Tip:对bash shell脚本来说,这两种不同的shell(默认的交互shell和默认的系统shell)会造成问题。

我们可以直接采用/bin/dash的指令进入dash shell程序,$提示符是dash的标志,若是需要退出,我们只需要键入exit指令即可。

在下一节将探究shell程序和新启动的shell程序之间的关系。

shell的父子关系

了解关系之前,需要知道什么是父shell,一般来说用于登录控制器终端或者在终端仿真器所启动的默认shell就是一个父shell,然后等待命令的输入。

在命令行输入/bin/dash的命令之后会创建一个全新的shell程序,那么这个程序就被称为子shell,也同样会等待命令的输入。

当我们生成子shell的时候,需要使用ps -f命令来帮助我们理清这一切,这个命令显示出显示出现有的进程:

在我们输入命令之后,一个子shell就出现了,第二个ps -f 是在子shell中执行的,其实在PID和PPID这两个号码之间,我们也能够看出端倪,现有的ID都是由父shell所创建而成。

在生成子shell进程时,只有部分的父进程的环境会被复制到子shell环境中,这会对包括变量在内的一些东西造成影响,子shell可以从父shell中创建,也可以从另一个子shell中创建(相当于一个树形结构中的分支)。

bash shell程序可以使用命令行参数修改shell的启动方式:

还有更多的参数可以使用man命令详细查看。

进程列表

为了避免命令过多需要一个又一个的输入,我们可以在一行中一次运行一系列的命令,这就是命令列表,只需要在命令之间加入分号(;)即可。

实例:

该实例中命令依次执行,不存在任何的问题,但这也仅仅是命令列表,不是进程列表,若是要变成进程列表,这些命令必须包含在括号中。

多了括号之后,在最终的结果来看并没有什么不同,但是多了括号之后,使得命令列表变成进程列表,生成一个子shell来执行对应的指令。

Tip:进程列表是一种命令分组(command grouping)。另一种命令分组是将命令放入花括号中,并在命令列表尾部加上分号(;)。语法为{ command; }。使用花括号进行命令分组并不会像进程列表那样创建出子shell。

在这个实例中最值得关注的就是是否生成子shell,我们需要借助一个环境变量来查看:(echo $BASH_SUBSHELL)。

如上图所示,显示的数字是0,说明这些命令不是在子shell中运行的。

若是使用进程列表的话,最后输出的数字会是1。甚至我们还可以命令列表中嵌套括号来创建子shell。

Tip:在shell脚本中,经常会使用子shell进行多线程处理,但是利用子shell的成本不菲,会明显的拖慢处理的速度,在一般交互式的shell会话中,子shell同样会存在问题。

子shell的用法

在交互式shell中,一个高效的子shell用法就是使用后台模式。

后台模式:运行命令可以在处理命令的同时让出CLI,以作他用。其中经典命令就是sleep。

sleep:接受一个参数,参数是希望进程等待的秒数,返回CLI提示符。

若是我们想要将命令置入后台模式,可以在命令末尾加上字符&。当命令被置入后台,在CLI提示符返回之前会出现两个信息,第一个信息是显示在方括号中的后台作业号,第二个是后台作业的进程ID。

实例:

后台作业号是2,进程ID是9370。

在这里我们除了使用ps命令来查看进程信息,也可以使用jobs命令来显示后台作业信息。

jobs命令:可以显示当前在进行作业的进程的信息。

方括号中是作业号,第二个是作业状态,以及对应的命令。

利用jobs命令中的-l选项,还可以看到更多相关的信息,还能显示PID。后台作业结束后就会显示已完成的状态。

Tip:需要提醒的是,后台作业的结束状态不是一直等待到合适的时候才会现身,会突然出现在屏幕上。

上面是将命令置入后台,若是我们将进程列表置入后台会有什么不一样的效果嘛

实例:

在CLI中运用子shell较好的方法之一就是将进程列表置入后台模式,既可以在子shell之中进行处理工作,也不会让子shell的I/O受制于终端。

除了将进程列表置入后台模式之外,还有另外一种方法也可以—协程。

协程:同时做两件事,在后台生成一个子shell,并在这个子shell之中执行命令。

命令:coproc command

实例:

同样的jobs命令可以显示协程的处理状态。

从上面的例子我们可以看到,子shell执行的后台指令是coproc COPROC sleep 10。COPROC是coprco命令给进程取的名字,我们自己可以使用扩展语法设置这个名字:

当然,使用扩展语法写起来还是有些许的麻烦,必须确保第一个花括号和命令之间有一个空格,结尾亦是如此。

Tip:协程使用起来非常的方便,只有当我们拥有多个协程的时候才需要对其命名,需要和它们进行通信。

协程和进程列表结合起来可以产生嵌套的子shell,只需要输入进程列表,然后在列表前加上命令coproc就可以了。

简单的介绍了子shell的几个用途,接下来我们来研究内建命令和外部命令的差异。

shell的内建命令

在shell的学习过程中,我们可能听说过内建命令和非内建命令(外部命令),这两种命令的操作方式也是大不相同。

外部命令

外部命令有时候也被称为文件系统命令,是存在于bash之外的程序,外部命令一般可能存在于/bin,/usr/bin之中。

ps命令其实就是一个外部命令,可以使用which和type命令来找到它,当外部命令执行的时候,会创建出一个子进程,这种操作被称为衍生,ps命令可以很方便的显示出它的父进程及对应的衍生程序。

Tip:就算衍生出子进程或是创建了子shell,仍然可以通过发送信号与其沟通,这一点在命令行和脚本编写都是很有用的。

内建命令

要说内建命令和外部命令的区别主要在于内建命令不需要使用子进程来执行,它们已经和shell编译成一体了,作为shell工具的组成部分,不用借助外部程序文件来运行。

cd和exit命令都内建于bash shell,也可以使用type命令来进行查看。

Tip:既不需要衍生出子进程来执行,也不需要打开程序文件,执行速度更快,效率也更高。

当然命令也不是这两种的分类,有些命令更是两者兼而有之,例如echo和pwd。

有一点需要注意,which命令只能显示外部命令文件,对于有多种实现的命令,我们若是想要外部命令来实现,可以直接输入/bin/pwd。

history命令

一个比较有用的内建命令是history命令,shell会跟踪使用过的命令,可以召回这些命令并且再次使用。

一般来说记录的命令会非常的多,需要谨慎使用。

Tip:我们可以设置保存在bash历史记录的命令数,要想实现这一点,需要修改名为HISTSIZE的环境变量。

若是我们只是需要唤回并使用最近的命令,只需要输入!!即可。

命令历史记录是被保存在隐藏的文件.bash_history中,位于主目录里,bash命令是先存放在内存中,当shell退出才写进历史文件。但是我们可以在退出会话之前将命令历史记录写入.bash_history文件,为了实现强制写入,需要使用history中的-a选项。

history和.bash_history的输出是一样的,除了最近的那条命令。

Tip:若是打开了多个终端会话,仍然可以使用history -a命令在打开的会话中向文件中添加记录,对于其他的终端会话,该命令却不会向里面添加记录,若是想要更新终端会话,可以使用history -n命令。

命令别名

alias命令是另一个shell的内建命令,命令别名允许为常用的命令创建另一个名称,降低输入量。

一般来说在系统初始会预设一些命令别名:

自己也可以创建命令的别名:

这个时候,我们就可以使用 li 命令来代替 ls -li 了。这个时候我们随时都可以使用它,在shell脚本中也可以,但是命令别名属于内部命令,一个别名只在被定义的shell进程中才有效。当然是有方法可以解决的,在之后的学习中会讲到。

总结

此篇主要是讨论了shell,包括shell进程及其关系,还有子shell,以及那些能够创建子进程的命令和不能创建子进程的命令。

1.当我们登录终端时,一般会启动一个交互式shell,系统启动那个主要取决于用户的配置,一般是bash或者dash。

2.子shell可以利用bash来生成,或者是使用进程列表,coproc命令也会产生shell,子shell也可以嵌套,生成子shell的子shell。

3.最后学习两种类型的命令:内建命令和外部命令。

下一篇将会学习linux的环境变量。