# 简介

以下是维基百科对 shell 的定义

普通意义上的 shell 就是可以接受用户输入命令的程序,Unix 操作系统下的 shell 既是用户交互的界面,也是控制系统的脚本语言。

所以我们可以理解,当你打开终端时,其实就是启动了一个 shellshell 有很多种,如 bash,zsh,csh 等。

/etc/passwd 中列出了当前用户默认的 shell

cyan@cyan-virtual-machine:/etc$ cat passwd
root:x:0:0:root:/root:/bin/bash
...
cyan:x:1000:1000:cyan,,,:/home/cyan:/bin/bash
...
mysql:x:127:133:MySQL Server,,,:/nonexistent:/bin/false

我们只看部分,可以看到使用的是 bash shell

# 父子 shell

系统一开始启动的 shell 是父 shell ,在此基础上启动其他 shell 或是使用命令 /bin/bash ,被称为子 shell 。当前进程下的 shell ,在生成子 shell 进程时,父进程的部分环境被复制到子 shell 中。

结合 ps -f / ps --forest 命令我们可以查看当前 shell 的关系

# 首先进入终端,然后使用 bash 开启一个新的 shell
cyan@cyan-virtual-machine:/$ bash
# 使用 ps -f 查看当前 shell,在 18:42 打开终端(父 shell,PID=67792),在 18:49 调用 bash,进入子
# shell(PID=69555,PPID=67792,PPID--Parent PID)
cyan@cyan-virtual-machine:/$ ps -f
UID          PID    PPID  C STIME TTY          TIME CMD
cyan       67792   67791  0 18:42 pts/1    00:00:00 -bash
cyan       69555   67792  0 18:49 pts/1    00:00:00 bash
cyan       69562   69555  0 18:49 pts/1    00:00:00 ps -f

这么看不是很形象,可以使用 --forest 选项

cyan@cyan-virtual-machine:/$ ps --forest
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69555 pts/1    00:00:00  \_ bash
  69564 pts/1    00:00:00      \_ ps

这里很明显 PID=69555bashPID=67792bash 的子 shell 。可是更下面为什么还有个 ps ,这是后话。

# 进程列表

如果你想一次性执行一系列命令,而不是执行一个输入一个,就可以在命令之间使用分号

cyan@cyan-virtual-machine:/$ pwd;ls;cd /etc;pwd;
/ #第一个 pwd 指令结果
bin   cdrom  etc   lib    lib64   lost+found  mnt  proc  run   snap  swapfile  tmp  var
boot  dev    home  lib32  libx32  media       opt  root  sbin  srv   sys       usr
/etc # 第二个 pwd 指令结果

还有一种方式就是使用进程列表,需要将这些命令加入括号中, (pwd;ls;cd /etc;pwd;ps -f) 。这样会创建一个子 shell 来执行对应命令,其实进程列表是一种命令分组。

cyan@cyan-virtual-machine:~/Templates$ ls;ps --forest;
new_File  test  test_1.sh
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69654 pts/1    00:00:00  \_ ps
  
cyan@cyan-virtual-machine:~/Templates$ (ls;ps --forest)
new_File  test  test_1.sh
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69641 pts/1    00:00:00  \_ bash
  69643 pts/1    00:00:00      \_ ps

很明显, (ls;ps --forest) 是在子 shell 中运行的。

其实,也可以通过查看环境变量 BASH_SUBSHELL 查看子 shell 是否创建,该环境变量返回子 shell 的个数。

cyan@cyan-virtual-machine:~/Templates$ (echo $BASH_SUBSHELL)
1

另一种进程分组: { pwd;ls;cd ~; } 花括号 + 末尾分号,但是不会创建子 shell, 需要注意的是,花括号与命令和分号之间存在空格

退出一个 shell 需要使用 exit ,要是使用 exit 时出现: logout There are stopped jobs. ,说明这个子 shell 中还存在 stopped 的进程,可以通过 kill -9 pid 强制杀死进程。

为了使读者对这个问题有更好的认识,现在编写以下脚本

#!/bin/bash
#脚本文件名 test
val=10
# 很明显,这是一个死循环
while val=10
do
        val=10
done

然后进入子 shell 执行该脚本,你会看到脚本在不断循环而不会结束,此时使用 ctrl+z 挂起这个线程

cyan@cyan-virtual-machine:~/Templates$ bash
cyan@cyan-virtual-machine:~/Templates$ ./test
^Z
[1]+  Stopped                 ./test

现在你就退不出子 shell 了,试试 exit 语句

cyan@cyan-virtual-machine:~/Templates$ exit
exit
There are stopped jobs.
cyan@cyan-virtual-machine:~/Templates$ ps
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69675 pts/1    00:00:00 bash
  69681 pts/1    00:00:01 test
  69683 pts/1    00:00:00 ps
cyan@cyan-virtual-machine:~/Templates$ exit
exit
There are stopped jobs.

很明显, exit 没有让你退出子 shell ,我们看到 test 进程的 PID 为 69681

cyan@cyan-virtual-machine:~/Templates$ kill -9 69681
cyan@cyan-virtual-machine:~/Templates$ ps
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69675 pts/1    00:00:00 bash
  69689 pts/1    00:00:00 ps
[1]+  Killed                  ./test
cyan@cyan-virtual-machine:~/Templates$ exit
exit

哦~有没有觉得自己一下子学了很多东西。

# 后台模式

在命令后使用 & , 将其置入后台模式,并且可以用 ps / jobs 来显示后台作业信息。 jobs 命令可以显示出当前运行在后台模式中的所有用户的进程。

cyan@cyan-virtual-machine:~/Templates$ sleep 1000 &
[1] 69698
cyan@cyan-virtual-machine:~/Templates$ jobs -l
[1]+ 69698 Running                 sleep 1000 &

1 是作业号, shell 中运行的每个进程被称为作业。后显示作业状态以及命令,-l 显示命令 PID。使用 & 将命令置入后台不会创建子 shell

你应该知道为什么要将命令执行置入后台,假设你有一个一直在循环的脚本,如果这个脚本不执行完,那你就不能输入其他命令。

# 协程

会后台生成子 shell ,子 shell 执行指令

cyan@cyan-virtual-machine:~/Templates$ coproc sleep 10
[1] 69704
cyan@cyan-virtual-machine:~/Templates$ ps --forest
    PID TTY          TIME CMD
  67792 pts/1    00:00:00 bash
  69706 pts/1    00:00:00  \_ ps
[1]+  Done                    coproc COPROC sleep 10

当然也可以和进程列表一起使用,还可以命名

coproc My_Job (ls;sleep 10)

My_Job 是进程的名字,默认是 COPROC。协程 coproc 与进程列表一起使用,会产生嵌套子 shell。需要牢记的是,生成子 shell 的成本不低

# 内建命令

现在不想更这个,鸽一下