#Linux #shell #开发
5.1 shell的类型
查看shell的类型:cat /etc/passwd
bash shell程序位于/bin目录内。bash shell是广为流行shell,很少有人使用其他的shell作为默认shell。
5.2 shell的父子关系
在CLI提示符后输入/bin/bash命令或其他等效的bash命令时,会创建一个新的shell程序。 这个shell程序被称为子shell(child shell)。
子shell也拥有CLI提示符,同样会等待命令输入。 当输入bash、生成子shell的时候,你是看不到任何相关的信息的,因此需要另一条命令帮助 我们理清这一切。
bash命令 开启子bask shell终端
-c string 从string中读取命令并进行处理
-i 启动一个能够接收用户输入的交互shell
-l 以登录shell的形式启动
-r 启动一个受限shell,用户会被限制在默认目录中
-s 从标准输入中读取命令
exit命令 不仅能退出子shell,还能用来登出当前的虚拟控制台终端或终端仿真器软件。只需要在父shell中输入exit,就能够从容退出CLI了。
5.2.1 进程列表
命令列表
在命令之间加入分号(;)可以依次运行的一系列命令。
$ pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls /home/Christine Desktop Downloads Music Public Videos Documents junk.dat Pictures Templates /etc /home/Christine Desktop Downloads Music Public Videos Documents junk.dat Pictures Templates
$
命令列表要想成为进程列表,这些命令必须包含在括号里。
$ (pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls) /home/Christine Desktop Downloads Music Public Videos Documents junk.dat Pictures Templates /etc /home/Christine Desktop Downloads Music Public Videos Documents junk.dat Pictures Templates
$
括号的加入使命令列表变成了进程列表,生成了一个子shell来执行对应的命令。
命令分组
进程列表是一种命令分组(command grouping)。另一种命令分组是将命令放入花括号中, 并在命令列表尾部加上分号(;)。语法为{ command; }。使用花括号进行命令分组并不 会像进程列表那样创建出子shell。
子shell多线程
要想知道是否生成了子shell,得借助一个使用了环境变量的命令。(环境变量会在[第6章]中详述。)这个命令就是echo $BASH_SUBSHELL。如果该命令返回0,就表明没有子shell。如果返回 1或者其他更大的数字,就表明存在子shell。
在shell脚本中,经常使用子shell进行多进程处理。但是采用子shell的成本不菲,会明显拖慢处理速度。在交互式的CLI shell会话中,子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O。
5.2.2 子shell用法
在交互式的shell CLI中,还有很多更富有成效的子shell用法。进程列表、协程和管道([第11 章]会讲到)都利用了子shell。它们都可以有效地在交互式shell中使用。 在交互式shell中,一个高效的子shell用法就是使用后台模式。
后台模式
在后台模式中运行命令可以在处理命令的同时让出CLI,以供他用。
sleep命令接受一个参数,该参数是你希望进程等待(睡眠)的秒数。这个命令在脚本中常 用于引入一段时间的暂停。命令sleep 10会将会话暂停10秒钟,然后返回shell CLI提示符。
将命令置入后台模式,可以在命令末尾加上**字符&**:
$ sleep 3000&
[1] 2396
$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 2338 2337 0 10:13 pts/9 00:00:00 -bash
christi+ 2396 2338 0 10:17 pts/9 00:00:00 sleep 3000 christi+ 2397 2338 0 10:17 pts/9 00:00:00 ps -f
$
sleep命令会在后台(&)睡眠3000秒(50分钟)。当它被置入后台,在shell CLI提示符返回 之前,会出现两条信息。第一条信息是显示在方括号中的 后台作业(background job)号(1)。 第二条是后台作业的进程ID(2396)。 ps命令用来显示各种进程。我们可以注意到命令sleep 3000已经被列出来了。在第二列显 示的进程ID(PID)和命令进入后台时所显示的PID是一样的,都是2396。 除了ps命令,你也可以使用 jobs命令来显示后台作业信息。
jobs命令可以显示出当前运行 在后台模式中的所有用户的进程(作业)。
-l 看到更多的相关信息。除了默认信息之外,还能够显示出命令的PID。
$ jobs
[1]+ Running sleep 3000 &
$ jobs -l
[1]+ 2396 Running sleep 3000 &
$
将进程列表置入后台
进程列表是运行在子shell中的一条或多条命令。使用包含了sleep命令的进程列 表,并显示出变量BASH_SUBSHELL,结果和期望的一样。
$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)
1
$
在上面的例子中,有一个2秒钟的暂停,显示出的数字1表明只有一个子shell,在返回提示符 之前又经历了另一个2秒钟的暂停。没什么大事。
将相同的进程列表置入后台模式会在命令输出上表现出些许不同。
$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)&
[2] 2401
$ 1
[2]+ Done ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
$
把进程列表置入后台会产生一个作业号和进程ID,然后返回到提示符。不过奇怪的是表明单 一级子shell的数字1显示在了提示符的旁边!不要不知所措,只需要按一下回车键,就会得到另 一个提示符。
在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式。你既可以在子shell中 进行繁重的处理工作,同时也不会让子shell的I/O受制于终端。
协程
协程可以同时做两件事。它在后台生成一个子shell,并在这个子shell中执行命令。
coproc命令 进行协程处理,并在子shell中执行的命令。
$ coproc sleep 10
[1] 2544
$
除了会创建子shell之外,协程基本上就是将命令置入后台模式。当输入coproc命令及其参 数之后,你会发现启用了一个后台作业。屏幕上会显示出后台作业号(1)以及进程ID(2544)。
jobs命令能够显示出协程的处理状态。
$ jobs
[1]+ Running coproc COPROC sleep 10 &
$
在上面的例子中可以看到在子shell中执行的后台命令是coproc COPROC sleep 10。COPROC 是coproc命令给进程起的名字。你可以使用命令的扩展语法自己设置这个名字。
$ coproc My_Job { sleep 10; }
[1] 2570
$
$ jobs
[1]+ Running coproc My_Job { sleep 10; } &
$
通过使用扩展语法,协程的名字被设置成My_Job。这里要注意的是,扩展语法写起来有点 麻烦。必须确保在第一个花括号({)和命令名之间有一个空格。还必须保证命令以分号(;)结 尾。另外,分号和闭花括号(})之间也得有一个空格。
注意事项
生成子shell的成本不低,而且速度还慢。尽量避免创建嵌套子shell。
5.3 理解shell的内建命令
5.3.1 外部命令
外部命令,又称为文件系统命令,是存在于bash shell之外的程序。它们并不是shell 程序的一部分。外部命令程序通常位于 /bin 、 /usr/bin、*/sbin*或 /usr/sbin中。
ps就是一个外部命令。你可以使用which和type命令找到它。
当外部命令执行时,会创建出一个子进程。这种操作被称为 衍生(forking)。外部命令 ps很方便显示出它的父进程以及自己所对应的衍生子进程。作为外部命令, ps命令执行时会创建出一个子进程。在这里, ps命令的PID是2801,父PID 是2743。作为父进程的bash shell的PID是2743。
$ ps -f
UID PID PPID C STIME TTY TIME CMD
christi+ 2743 2742 0 17:09 pts/9 00:00:00 -bash
christi+ 2801 2743 0 17:16 pts/9 00:00:00 ps -f
$
下图展示了外部命令执行时的衍生过程。
![[Pasted image 20230104171347.png]]
就算衍生出子进程或是创建了子shell,你仍然可以通过发送信号与其沟通,这一点无论是在命令行还是在脚本编写中都是极其有用的。发送信号(signaling)使得进程间可以通过 信号进行通信。信号及其发送会在[第16章]中讲到。
5.3.2 内建命令
内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成了一 体,作为shell工具的组成部分存在。不需要借助外部程序文件来运行。
因为既不需要通过衍生出子进程来执行,也不需要打开程序文件,内建命令的执行速度要更快,效率也更高。
使用history命令
history命令 bash shell会跟踪你用过的命令。你可以唤回这些命令并重新使用。
通常历史记录中会保存最近的1000条命令。
你可以设置保存在bash历史记录中的命令数。要想实现这一点,你需要修改名为HISTSIZE 的环境变量(参见[第6章])。
命令历史记录被保存在隐藏文件 .bash_history中,它位于用户的主目录中。这里要注意的是, bash命令的历史记录是先存放在内存中,当shell退出时才被写入到历史文件中。
-a 在退出shell会话之前强制将命令历史记录写入 .bash_history文件
以唤回并重用历史列表中最近的命令。这样能够节省时间和击键量。**输入!!**,然后按回 车键就能够唤出刚刚用过的那条命令来使用。
当输入!!时,bash首先会显示出从shell的历史记录中唤回的命令。然后执行该命令。
你可以唤回历史列表中任意一条命令。只需输入惊叹号和命令在历史列表中的编号即可。
命令别名
alias命令是另一个shell的内建命令。命令别名允许你为常用的命令(及其参数)创建另一 个名称,从而将输入量减少到最低。
-p 查看当前可用的别名
alias li='ls -li' 使用alias命令创建属于自己的别名。
在定义好别名之后,你随时都可以在shell中使用它,就算在shell脚本中也没问题。要注意, 因为命令别名属于内部命令,一个别名仅在它所被定义的shell进程中才有效。