# 简介
以下是维基百科对 shell
的定义
普通意义上的 shell 就是可以接受用户输入命令的程序,Unix 操作系统下的 shell 既是用户交互的界面,也是控制系统的脚本语言。
所以我们可以理解,当你打开终端时,其实就是启动了一个 shell
, shell
有很多种,如 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=69555
的 bash
是 PID=67792
的 bash
的子 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 的成本不低。
# 内建命令
现在不想更这个,鸽一下