Shell 的类型
用户登录某个虚拟控制台终端或在GUI中运行终端仿真器时,会启动用户对应的 shell 程序。 在 /etc/passwd 文件中,用户记录的第 7 个字段指定了用户默认的 shell. 比如下面 root 用户使用 GNU bash shell 作为自己的默认shell 程序:
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bash shell 程序位于 /bin 目录内,是一个可执行文件:
$ ls -lF /bin/bash
-rwxr-xr-x 1 root root 1216928 Apr 18 2019 /bin/bash*
/bin 内还有其他 shell 程序:
$ ls -lF /bin/*sh
-rwxr-xr-x 1 root root 1216928 Apr 18 2019 /bin/bash*
-rwxr-xr-x 1 root root 129536 Jan 18 2019 /bin/dash*
lrwxrwxrwx 1 root root 4 Apr 18 2019 /bin/rbash -> bash*
lrwxrwxrwx 1 root root 4 Jan 2 20:22 /bin/sh -> dash*
当用户没登陆时,系统也会执行一些脚本,这时系统的默认 shell 是 /bin/sh,从上面的输出中可以看出,/bin/sh 软链接到了 /bin/dash,因为我使用的是 Debian,这是 Debian 的默认 shell.
我们可以改变当前使用的 shell,比如可以直接输入 /bin/dash 来启动 dash shell:
$ /bin/dash
$
那我们怎么知道当前用的是什么 shell 呢?一个简单的方法就是输入一条错误命令:
$ hello
/bin/dash: 4: hello: not found
shell 的父子关系
登录到某个终端时的 shell 是父shell,当输入 /bin/bash
或其他 bash 命令时(就是上面提的命令),会创建一个新的 shell,称为子shell.
$ bash
$ ps --forest
PID TTY TIME CMD
20246 pts/3 00:00:00 bash
5862 pts/3 00:00:00 \_ bash
8389 pts/3 00:00:00 \_ ps
$ exit
$ ps --forest
PID TTY TIME CMD
20246 pts/3 00:00:00 bash
11334 pts/3 00:00:00 \_ ps
创建子 shell 时,部分父 shell 的环境被复制到子 shell 环境中。
另外,运行 shell 脚本也会创建子 shell.
进程列表
要想在一行中依次执行一系列命令,可以用 命令列表 来实现,只需要用分号隔开命令即可:
$ pwd; ls; cd /etc; ls; cd
如果将上述命令用括号括起来,就是 进程列表。进程列表会生成一个子 shell 来执行对应的命令:
$ (pwd; ls; cd /etc; ls; cd)
我们可以通过查看 $BASH_SUBSHELL
的值来判断是否生成了子 shell:
$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
$ ( (echo $BASH_SUBSHELL) )
2
在父 shell 时,$BASH_SUBSHELL 为 0;每生成一层子 shell,$BASH_SUBSHELL 的值就加 1.
子 shell 搭配后台模式
当我们输入一个命令后,要等到命令执行完毕才能输入下一个命令。如果想在后台执行命令,可以在命令后面加 &
:
$ sleep 3 #等待3秒
$ sleep 3&
[1] 8792
$ ps -f
UID PID PPID C STIME TTY TIME CMD
pi 8792 20246 0 16:39 pts/3 00:00:00 sleep 3
pi 8799 20246 0 16:39 pts/3 00:00:00 ps -f
pi 20246 20245 0 14:12 pts/3 00:00:00 -bash
当命令置入后台时,会出现两条信息:第一条是方括号中的后台作业号(上面的 [1]
),第二条是后台作业的进程 ID(上面的 8792
)
我们可以用 ps -f
或 jobs
来查看后台作业信息。
$ sleep 3&
[1] 17718
pi@raspbian:~$ jobs -l
[1]+ 17718 Running sleep 3 &
利用后台模式,我们可以将进程列表放到后台:
$ (sleep 2; echo $BASH_SUBSHELL; sleep 2)&
[1] 23123
$ 1
我们可以用 协程 命令 coproc
来完成生成子 shell 和执行命令两件事:
$ coproc sleep 10
[1] 24457
$ jobs
[1]+ Running coproc COPROC sleep 10 &
COPROC 是 coproc
命令给进程起的名字,用于协程之间通信用。我们也可以自定义名字:
$ coproc My_job { sleep 10; }
[1] 25399
上面我们用到了 扩展语法,即 { sleep 10; }
,注意命令要用分号结尾,并且与左右花括号之间有一个空格。
内建命令
外部命令,也叫 文件系统命令,是存在于 bash shell 之外的程序,一般位于 /bin、/usr/bin、/sbin、/usr/sbin 中。比如 ps
就是外部命令,我们可以用 which
或 type
找到它的位置:
$ which ps
/bin/ps
$ type -a ps
ps is /bin/ps
$ ls -l /bin/ps
-rwxr-xr-x 1 root root 125088 May 31 2018 /bin/ps
外部命令在执行的时候,会先创建一个子进程,称为 衍生 forking
$ ps -f
UID PID PPID C STIME TTY TIME CMD
pi 6919 17241 0 19:35 pts/3 00:00:00 ps -f
pi 17241 17240 0 18:35 pts/3 00:00:00 -bash
从上面可以看出,ps 进程的父进程ID(PPID)是 17241,也就是下面那个。
外部命令因为需要进行衍生,所有花费的时间稍多一丢丢。
内建命令则相反,它本身和 shell 编译成了一体,不需要外部程序文件来运行。比如 cd
和 exit
,我们可以用 type
来检验:
$ type cd
cd is a shell builtin
$ type exit
exit is a shell builtin
某些命令即有内部实现,也有外部实现,比如:echo
和 pwd
,默认是使用内部实现,如果要用外部实现,可以手动指明其对应的外部文件。
$ type echo #默认是内建命令
echo is a shell builtin
$ type -a echo #列出不同实现
echo is a shell builtin
echo is /bin/echo
$ which echo
/bin/echo