Skip to content

理解shell

shell不只是一种简单的CLI, 他是一个时刻在运行的复杂的交互式程序

shell的类型

启动什么样的shell取决于个人用户ID的设置, 在/etc/passwd文件中

bash
6jiao:x:1000:1000:jiao,,,:/home/jiao:/bin/bash
-rwxr-xr-x 1 root root 1392424 10月  7  2021 /bin/bash*

是一个可执行文件, 一般还包含有其他的shell, 但是由于bash shell广为流传, 所以很少使用别的shell作为默认的shell

默认的shell会在用户登录的时候启动, 不过还有一个默认的shell是/bin/sh, 作为默认的系统shell, 用于那些需要启动时候使用的系统shell脚本

有的发行版会把默认的系统shell使用软连接设置为bash shell,

在有的发行版上默认的系统shell和交互shell并不相同

bash
lrwxrwxrwx 1 root root 4  3月 11 21:38 /bin/sh -> dash   //Ubuntu使用dash

对于shell脚本来说, 由于shell的不同, 会导致问题

可以更改默认的交互shell, 直接输入文件名就可以了, 使用exit退出

bash
jiao@jiao-virtual-machine:~/桌面/linux-shell/3$ /bin/dash
$ ls
3  abb	acc  baa  num.txt.gz  test.tar.gz  tset
$ cd ..
$ ls
1  3  test.tar
$ exit

shell的父子关系

用于登录某个虚拟控制器终端或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父shell。父shell提供CLI提示符,然后等待命令输入。

在CLI提示符后输入/bin/bash命令或其他等效的bash命令时,会创建一个新的shell程序。这个shell程序被称为子shell(child shell)。子shell也拥有CLI提示符,同样会等待命令输入

bash
$ ps --forest
    PID TTY          TIME CMD
   2753 pts/0    00:00:00 bash
   6135 pts/0    00:00:00  \_ dash
   6139 pts/0    00:00:00      \_ ps

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

参数描述
-c string从string中读取命令并进行处理
-i启动一个能够接收用户输入的交互shell
-l(小写L)以登录shell的形式启动
-r启动一个受限shell,用户会被限制在默认目录中
-s从标准输入中读取命令

进程列表

你可以在一行中指定要依次运行的一系列命令。这可以通过命令列表来实现,只需要在命令之间加入分号(;)即可

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

bash
pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls

这不是一个进程列表

bash
(pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls )

这是一个进程列表, 会产生一个子shell来运行

bash
(pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; echo $BASH_SUBSHELL)

最后一个命令可以显示运行的shell数量

可以使用多个括来创建子shell的子shell, 在脚本中经常使用子shell来进行多进程处理, 但是采用子shell的成本不菲,会明显拖慢处理速度。在交互式的CLI shell会话中,子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O

别出心裁的shell用法

在交互式的shell CLI中还有很多用法, 进程列表, 协程, 管道等

后台模式

在后台模式运行命令可以在处理命令的同时让出CLI, 想要进入后台模式, 可以在末尾加上&

bash
jiao@jiao-virtual-machine:~$ sleep 3000 &
[1] 2778

显示为后台作业一号, 任务标号为2778

bash
$ jobs
[1]+  运行中               sleep 3000 &

显示所有的后台任务

-l(小写L): 显示详细的信息, 显示任务号

把进程列表置入后台

进程列表是运行在子shell中的一条或多条命令

bash
jiao@jiao-virtual-machine:~$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)
1
jiao@jiao-virtual-machine:~$ (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)&
[2] 2818
jiao@jiao-virtual-machine:~$ 1

[2]+  已完成               ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
jiao@jiao-virtual-machine:~$

把进程;列表放入后台就可以在子shell中处理繁重的任务, 也可以让子shell的I/O受到终端控制

协程

同时做两件事, 后台生成一个子shell, 并在子shell中执行任务

bash
$ coproc sleep 10 [1] 2544
[2] 2850
$ jobs
[1]-  运行中               sleep 3000 &
[2]+  运行中               coproc COPROC sleep 10 &

coproc COPROC sleepcoproc进程起的名字, 可以使用扩展语法来对其命名

bash
jiao@jiao-virtual-machine:~$ coproc My_Job { sleep 10; }
[2] 2874
jiao@jiao-virtual-machine:~$ jobs
[1]-  运行中               sleep 3000 &
[2]+  运行中               coproc Myjob { sleep 10; } &
jiao@jiao-virtual-machine:~$ 
[2]+  已完成               coproc Myjob { sleep 10; }

必须确保在第一个花括号({)和命令名之间有一个空格。还必须保证命令以分号(;)结尾。另外,分号和闭花括号(})之间也得有一个空格。

生成子shell的成本不低,而且速度还慢。创建嵌套子shell更是火上浇油

理解shell的内建命令

type命令

用来查看命令是内部命令还是外部

-t: 监测种类

  • 别名: alias
  • 内部命令: builtin
  • 外部命令: file

-p: 是外部命令才会显示文件名

-a: 会在环境变量中把文件找出来, 包括别名

外部命令

也称为文件系统命令, 存在于bash shell之外的程序, 不是bash shell的一部分, 外部命令通常位于/bin, /usr/bin, /sbin, /usr/sbin中

可以使用which和type命令查看

bash
jiao@jiao-virtual-machine:~/桌面$ which ps
/usr/bin/ps
jiao@jiao-virtual-machine:~/桌面$ type -a ps
ps /usr/bin/ps
ps /bin/ps

当外部的命令执行的时候会创建一个新的子进程, 需要花费精力来配置新的子进程的环境

创建的进程之间可以使用信号来通讯

内建命令

不需要子进程来进行, 已经和shell编译为一个整体, 作为shell的工具

bash
jiao@jiao-virtual-machine:~/桌面$ type exit
exit shell 内建
jiao@jiao-virtual-machine:~/桌面$ type cd 
cd shell 内建

内建命令的效率更高

有的命令会有多种实现

bash
jiao@jiao-virtual-machine:~/桌面$ type -a echo
echo shell 内建
echo /usr/bin/echo
echo /bin/echo

history

显示使用过的命令列表

可以修改HISTSIZE环境变量来确定保存的记录数量

使用!!可以再次使用上一条命令

使用过的命令保存在.bash_history, 位于用户的主目录之中, 历史记录先存放在内存之中shell退出的时候才会写入

可以使用history -a命令强制写入

如果打开了多个终端, 使用-a添加之后其他的终端不会更新, .bash_history只有在第一次被打开的时候才会被读取, 可以使用history -n更新

使用!n, 可以再次使用记录中的第n个命令

命名别名

alias是shell的内建命令, 允许你为常用的命令起别名

bash
alias -p

查看已经存在的别名

bash
alias li='ls -l'

由于是内部命令, 所以可以在脚本中使用, 但是只能在定义他的shell中, 命令是暂时有效的, 保存在~/.bashrc或者/etc/bashrc之后使用source ~/.bashrc就可以