理解shell

shell的类型

系统启动什么样的shell程序取决于你个人的用户ID配置。在/etc/passwd文件中,在用户ID记录的第7个字段中列出了默认的shell程序。只要用户登录到某个虚拟控制台终端或是在GUI中启动终端仿真器,默认的shell程序就会开始运行。

在下面的例子中,用户rico使用GNU bash shell作为自己的默认shell程序:


[root@CloudVM ~]# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
rico:x:1000:1000:shenyangrico,homeoffice,13941279880:/home/rico:/bin/bash
[root@CloudVM ~]# 

bash shell程序位于/bin目录内。从长列表中可以看出/bin/bash (bash shell)是一个可执行程序:

[root@CloudVM ~]# ls -lF /bin/bash
-rwxr-xr-x 1 root root 964608 Oct 31 01:07 /bin/bash*
[root@CloudVM ~]# 

默认的交互shell会在用户登录某个虚拟控制台终端或在GUI中运行仿真器时启动。不过还有另外一个默认shell是/bin/sh,它作为默认的系统shell,用于那些需要在启动时使用的系统shell脚本。

你经常会看到某些发行版使用软链接将默认的系统shell设置成bash shell,如本实验环境使用的CentOS发行版:

[root@CloudVM ~]# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Dec 12 15:57 /bin/sh -> bash
[root@CloudVM ~]# 

但要注意的是在有些发行版上,默认的系统shell和默认的交互shell并不相同。

窍门 对bash shell脚本来说,这两种不同的shell(默认的交互shell和默认的系统shell)会造成问题。一定要阅读有关bash shell脚本首行的语法要求,以避免这些麻烦。

并不是必须一直使用默认的交互shell。可以使用发行版中所有可用的shell,只需要输入对应的文件名就行了。例如,你可以直接输入命令/bin/dash来启动dash shell。

可以输入exit来退出dash shell。

shell的父子关系

用于登录某个虚拟控制器终端或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父shell

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

在生成子shell的前后,使用ps -f命令显示子shell的进程ID(PID),父进程ID(PPID),来显示这种父子进程关系。

说明 进程就是正在运行的程序。bash shell是一个程序,当它运行的时候,就成为了一个进程。一个运行着的shell就是某种进程而已。因此,在说到运行一个bash shell的时候,你经常会看到"shell"和"进程"这两个词交换使用。

在生成子shell进程时,只有部分进程的环境被复制到子shell环境中。这会对包括变量在内的一些东西造成影响。

子shell(child shell,也叫subshell)可以从父shell中创建,也可以从另外一个子shell中创建。ps —forest命令展示了这些子shell间的嵌套结构。

[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     10727 10725  0 01:08 pts/0    00:00:00 -bash
root     13693 10727  0 02:08 pts/0    00:00:00 ps -f
[root@CloudVM ~]# bash
[root@CloudVM ~]# bash
[root@CloudVM ~]# bash
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
10727 pts/0    00:00:00 bash
13710 pts/0    00:00:00  \_ bash
13722 pts/0    00:00:00      \_ bash
13735 pts/0    00:00:00          \_ bash
13751 pts/0    00:00:00              \_ ps
[root@CloudVM ~]# 

ps -f命令也能够表现子shell的嵌套关系,因为它能够通过PPID列显示出谁是谁的父进程。

[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     10727 10725  0 01:08 pts/0    00:00:00 -bash
root     13710 10727  0 02:08 pts/0    00:00:00 bash
root     13722 13710  0 02:08 pts/0    00:00:00 bash
root     13735 13722  0 02:09 pts/0    00:00:00 bash
root     13801 13735  0 02:10 pts/0    00:00:00 ps -f
[root@CloudVM ~]# 

可以利用exit命令有条不紊地退出子shell。

[root@CloudVM ~]# exit
exit
[root@CloudVM ~]# exit
exit
[root@CloudVM ~]# exit
exit
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
10727 pts/0    00:00:00 bash
13992 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# 

exit命令不仅能退出子shell,还能用来登出当前的虚拟控制台终端或终端仿真器软件。只需要在父shell中输入exit,就能够从容退出CLI了。

运行shell脚本也能够创建出子shell。

就算时不使用bash命令或是运行shell脚本,你也可以生成子shell。一种方法就是使用进程列表。

进程列表

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

[root@CloudVM ~]# pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
/etc
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
[root@CloudVM ~]# 

在上面的例子中,所有的命令依次执行,不存在任何问题。不过这并不是进程列表。命令列表要想成为进程列表,这些命令必须包括在括号里。

[root@CloudVM ~]# (pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls)
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
/etc
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
[root@CloudVM ~]# 

尽管多出来的括号看起来没有什么太大的不同,但起到的效果确实非同寻常。括号的加入使命令列表变成了进程列表,生成了一个子shell来执行对应的命令。

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

要想知道是否生成了子shell,得借助一个使用了环境变量的命令。这个命令就是echo $BASH_SUBSHELL。如果该命令返回0,就表明没有子shell。如果返回1或者其他更大的数字,就表明存在子shell。

下面的例子中使用了一个命令列表,列表尾部是echo $BASH_SUBSHELL。

[root@CloudVM ~]# pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; echo $BASH_SUBSHELL
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
/etc
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
0
[root@CloudVM ~]# 

在命令输出的最后,显示的是数字0。这就表明这些命令不是在子shell中运行的。

要是使用进程列表的话,结果就不一样了。在列表最后加入echo $BASH_SUBSHELL。

[root@CloudVM ~]# (pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; echo $BASH_SUBSHELL)
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
/etc
/root
newdir  newfile  oldshell.sh  printenv_output  set_output  testdir
1
[root@CloudVM ~]# 

这次在命令输入的最后显示出了数字1。这表明的确创建了子shell,并用于执行这些命令。

所以说,进程列表就是使用括号包围起来的一组命令,它能够创建出子shell来这行这些命令。你甚至可以在进程列表中嵌套括号来创建子shell的子shell。

[root@CloudVM ~]# (pwd ; echo $BASH_SUBSHELL ; ps -f ; ps --forest)
/root
1
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     19866 17673  0 02:08 pts/0    00:00:00 -bash
root     19867 19866  0 02:08 pts/0    00:00:00 ps -f
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
19866 pts/0    00:00:00  \_ bash
19868 pts/0    00:00:00      \_ ps
[root@CloudVM ~]# (pwd ; echo $BASH_SUBSHELL ; ps -f ; ps --forest ; (pwd ; echo $BASH_SUBSHELL ; ps -f ; ps --forest))
/root
1
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     19951 17673  0 02:10 pts/0    00:00:00 -bash
root     19952 19951  0 02:10 pts/0    00:00:00 ps -f
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
19951 pts/0    00:00:00  \_ bash
19953 pts/0    00:00:00      \_ ps
/root
2
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     19951 17673  0 02:10 pts/0    00:00:00 -bash
root     19954 19951  0 02:10 pts/0    00:00:00 -bash
root     19955 19954  0 02:10 pts/0    00:00:00 ps -f
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
19951 pts/0    00:00:00  \_ bash
19954 pts/0    00:00:00      \_ bash
19956 pts/0    00:00:00          \_ ps
[root@CloudVM ~]# 

注意,在第一个进程列表中,数字1表明了一个子shell,这个结果和预期的一样。但是在第二个进程列表中,在命令echo $BASH_SUBSHELL外面又多出了一对括号。这对括号在子 shell中产生了另一个子shell来执行命令。因此数字2表明的就是这个子shell。

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

别处心裁的子shell用法

在交互式的shell CLI中,还有很多富有成效的子shell用法。进程列表、协程和管道都利用了子shell。它们都可以有效地在交互式shell中使用。

在交互式shell中,一个高效的子shell用法就是使用后台模式。在讨论如何将后台模式与子shell搭配使用之前,你得先搞明白什么是后台模式。

探索后台模式

在后台模式中运行命令可以在处理命令的同时让出CLI,以供他用。演示后台模式的一个经典命令就是sleep。

sleep命令接受一个参数,该参数是你希望进程等待(睡眠)的秒数。这个命令在脚本中常用于引入一段时间的暂停。命令sleep 10会将会话暂停10秒钟,然后返回shell CLI提示符。

[root@CloudVM ~]# sleep 10
[root@CloudVM ~]# 

要想将命令置入后台模式,可以在命令末尾加上字符&。把sleep命令置入后台模式可以让我们利用ps命令来小窥一番。

[root@CloudVM ~]# sleep 3000&
[1] 10843
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      7744  7742  0 11:09 pts/0    00:00:00 -bash
root     10843  7744  0 12:11 pts/0    00:00:00 sleep 3000
root     10855  7744  0 12:12 pts/0    00:00:00 ps -f
[root@CloudVM ~]# 

sleep命令会在后台(&)睡眠3000秒(50分钟)。当它被置入后台,在shell CLI提示符返回之前,会出现两条信息。第一条信息是显示在方括号中的后台作业(background job)号(1)。第二条是后台作业的进程ID(10843)。

ps命令用来显示各种进程。我们可以注意到命令sleep 3000已经被列出来了。在第二列显示的进程ID(PID)和命令进入后台时显示的PID是一样的,都是10843。

除了ps命令,你也可以使用jobs命令来显示后台作业信息。jobs命令可以显示出当前运行在后台模式中的所有用户的进程(作业)。

[root@CloudVM ~]# jobs
[1]+  Running                 sleep 3000 &
[root@CloudVM ~]# 

jobs命令在方括号中显示出作业号(1)。它还显示了作业的当前状态(running)以及对应的命令(sleep 3000&)。

利用jobs命令的-l(字母L的小写形式)选项,你还能够看到更多的相关信息。除了默认信息之外,-l选项还能够显示出命令的PID。

[root@CloudVM ~]# jobs -l
[1]+ 10843 Running                 sleep 3000 &
[root@CloudVM ~]# 

一旦后台作业完成,就会显示出结束状态。

[1]+  Done                    sleep 3000

窍门 需要提醒的是:后台作业的结束状态可未必会一直等待到合适的时候才现身。当作业结束状态突然出现在屏幕上的时候,你可别吃惊啊。

后台模式非常方便,它可以让我们在CLI中创建出有实用价值的子shell。

将进程列表置入后台

之前说过,进程列表是运行在子shell中的一条或多条命令。使用包含了sleep命令的进程列表,并显示出变量BASH_SUBSHELL,结果和期望的一样。

[root@CloudVM ~]# (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)
1
[root@CloudVM ~]# 

在上面的例子中,有一个2秒钟的暂停,显示出的数字1表明只有一个子shell,在返回提示符之前又经历了另一个2秒钟的暂停。没什么大事。

将相同的进程列表置入后台模式会在命令输出上表现出些许不同。

[root@CloudVM ~]# (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)&
[2] 11922
[root@CloudVM ~]# 1

[2]+  Done                    ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
[root@CloudVM ~]# 

把进程列表置入后台会产生一个作业号和进程ID,然后返回到提示符。不过奇怪的是表明单一级子shell的数字1显示在了提示符的旁边!不要不知所措,只需要按一下回车键,就会得到另一个提示符。

在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式。你既可以在子shell中进行繁重的处理工作,同时也不会让子shell的I/O受制于终端。

当然了,sleep和echo命令的进程列表只是作为一个示例而已。使用tar创建备份文件是有效利用后台进程列表的一个更实用的例子。

[root@CloudVM ~]# (tar -cf testdir.tar /root/testdir ; tar -cf rico.tar /home/rico)&
[1] 15711
[root@CloudVM ~]# tar: Removing leading `/' from member names
tar: Removing leading `/' from member names

[1]+  Done                    ( tar -cf testdir.tar /root/testdir; tar -cf rico.tar /home/rico )

将进程列表置入后台模式并不是子shell在CLI中仅有的创造性用法。协程就是另一种方法。

协程

协程可以同时做两件事。它在后台生成一个子shell,并在这个子shell中执行命令。

要进行协程处理,得使用coproc命令,还有要在子shell中执行的命令。

[root@CloudVM ~]# coproc sleep 10
[1] 18804
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     18804 17673  0 01:47 pts/0    00:00:00 sleep 10
root     18806 17673  0 01:47 pts/0    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
18804 pts/0    00:00:00  \_ sleep
18810 pts/0    00:00:00  \_ ps

不带扩展语法的coproc命令并没有创建子shell,协程基本上就是将命令置入后台模式。当输入coproc命令及其参数之后,你会发现启用了一个后台作业。屏幕上会显示出后台作业号(1)以及进程ID(18804)。

jobs命令能够显示出协程的处理状态。

[root@CloudVM ~]# jobs
[1]+  Running                 coproc COPROC sleep 10 &
[root@CloudVM ~]# jobs
[1]+  Done                    coproc COPROC sleep 10

在上面的例子中可以看到在子shell中执行的后台命令是coproc COPROC sleep 10。COPROC是coproc命令给进程起的名字。你可以使用命令的扩展语法自己设置这个名字。

[root@CloudVM ~]# coproc My_Job { sleep 10; }
[1] 18834
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     18834 17673  0 01:48 pts/0    00:00:00 -bash
root     18835 18834  0 01:48 pts/0    00:00:00 sleep 10
root     18838 17673  0 01:48 pts/0    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
18834 pts/0    00:00:00  \_ bash
18835 pts/0    00:00:00  |   \_ sleep
18842 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# jobs
[1]+  Running                 coproc My_Job { sleep 10; } &
[root@CloudVM ~]# jobs
[1]+  Done                    coproc My_Job { sleep 10; }
[root@CloudVM ~]# 

带扩展语法的coproc命令创建了一个子shell,并将命令置入后台模式。

通过使用扩展语法,协程的名字被设置成My_Job。这里需要注意的是,扩展语法写起来有点麻烦。必须确保在第一个花括号({)和命令名之间有一个空格。还必须保证命令以分号(;)结尾。另外,分号和闭花括号(})之间也得有一个空格。

说明 协程能够让你尽情发挥想象力,发送或接收来自shell中进程的信息。只有在拥有多个进程的时候才需要对协程进行命名,因为你得和它们进行通信。否则的话,让coproc命令将其设置成默认的名字COPROC就行了。

你可以发挥才智,将协程于进程列表结合起来产生嵌套的子shell。探索以下4种情况。

探索1 输入进程列表,然后把命令coproc放在前面:

[root@CloudVM ~]# coproc (sleep 10 ; sleep 2)
[1] 20273
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     20273 17673  0 02:16 pts/0    00:00:00 -bash
root     20274 20273  0 02:16 pts/0    00:00:00 sleep 10
root     20278 17673  0 02:16 pts/0    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
20273 pts/0    00:00:00  \_ bash
20274 pts/0    00:00:00  |   \_ sleep
20281 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# jobs
[1]+  Running                 coproc COPROC ( sleep 10; sleep 2 ) &
[root@CloudVM ~]# jobs
[1]+  Done                    coproc COPROC ( sleep 10; sleep 2 )
[root@CloudVM ~]# 

在上面的例子中,只有进程列表产生了一个子shell,coproc没有创建子shell,它只是将命令置入后台模式。

探索2 输入进程列表,然后把命令coproc放在前面,扩展语法设置协程的名字:

[root@CloudVM ~]# coproc My_Job (sleep 10 ; sleep 2)
[1] 21903
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     21903 17673  0 02:49 pts/0    00:00:00 -bash
root     21904 21903  0 02:49 pts/0    00:00:00 sleep 10
root     21907 17673  0 02:49 pts/0    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
21903 pts/0    00:00:00  \_ bash
21904 pts/0    00:00:00  |   \_ sleep
21917 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# jobs
[1]+  Running                 coproc My_Job ( sleep 10; sleep 2 ) &
[1]+  Done                    coproc My_Job ( sleep 10; sleep 2 )
[root@CloudVM ~]# 

在上面的例子中,只有进程列表产生了一个子shell,coproc同样没有创建子shell,它只是将命令置入后台模式。

探索3 输入进程列表,然后把命令coproc放在前面,使用花括号命令分组:{ command; }

[root@CloudVM ~]# coproc { (sleep 10 ; sleep 2); }
[1] 23990
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     23671 23668  0 03:24 pts/1    00:00:00 -bash
root     23990 23671  0 03:30 pts/1    00:00:00 -bash
root     23991 23990  0 03:30 pts/1    00:00:00 -bash
root     23992 23991  0 03:30 pts/1    00:00:00 sleep 10
root     23995 23671  0 03:30 pts/1    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
23671 pts/1    00:00:00 bash
23990 pts/1    00:00:00  \_ bash
23991 pts/1    00:00:00  |   \_ bash
23992 pts/1    00:00:00  |       \_ sleep
24000 pts/1    00:00:00  \_ ps
[root@CloudVM ~]# jobs
[1]+  Running                 coproc COPROC { ( sleep 10; sleep 2 ); } &
[root@CloudVM ~]# jobs
[1]+  Done                    coproc COPROC { ( sleep 10; sleep 2 ); }
[root@CloudVM ~]# 

在上面的例子中,产生了子shell的嵌套,coproc创建了一个子shell,在这个子shell当中,进程列表又产生了一个子shell的子shell,同时,coproc将命令置入后台模式。

探索4 输入进程列表,然后把命令coproc放在前面,扩展语法设置协程的名字,使用花括号命令分组:{ command; }

[root@CloudVM ~]# coproc My_Job { (sleep 10 ; sleep 2); }
[1] 23315
[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     17673 17670  0 01:25 pts/0    00:00:00 -bash
root     23315 17673  0 03:17 pts/0    00:00:00 -bash
root     23316 23315  0 03:17 pts/0    00:00:00 -bash
root     23317 23316  0 03:17 pts/0    00:00:00 sleep 10
root     23321 17673  0 03:17 pts/0    00:00:00 ps -f
[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
17673 pts/0    00:00:00 bash
23315 pts/0    00:00:00  \_ bash
23316 pts/0    00:00:00  |   \_ bash
23317 pts/0    00:00:00  |       \_ sleep
23324 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# jobs
[1]+  Running                 coproc My_Job { ( sleep 10; sleep 2 ); } &
[root@CloudVM ~]# jobs
[1]+  Done                    coproc My_Job { ( sleep 10; sleep 2 ); }
[root@CloudVM ~]# 

在上面的例子中,产生了子shell的嵌套,coproc创建了一个子shell,在这个子shell当中,进程列表又产生了一个子shell的子shell,同时,coproc将命令置入后台模式。

窍门 需要提醒的是:coproc { command; }这里需要注意的是,必须确保在第一个花括号({)和命令名之间有一个空格。还必须保证命令以分号(;)结尾。另外,分号和闭花括号(})之间也得有一个空格。这是coproc是否会生成子shell的关键。

理解Shell的内建命令

内建命令和非内建命令的操作方式大不相同。

外部命令

外部命令,有时候也被称为文件系统命令,是存在于bash shell之外的程序。它们并不是shell程序的一部分。外部命令程序通常位于/bin、/usr/bin、/sbin或/usr/sbin中。

ps就是一个外部命令。你可以使用which和type命令找到它。

[root@CloudVM ~]# which ps
/usr/bin/ps
[root@CloudVM ~]# type -a ps
ps is /usr/bin/ps
[root@CloudVM ~]# ls -l /usr/bin/ps
-rwxr-xr-x 1 root root 100208 Oct 31 00:58 /usr/bin/ps
[root@CloudVM ~]# 

当外部命令执行时,会创建出一个子进程。这种操作被称为衍生(forking)。外部命令ps很方便显示出它的父进程以及自己所对应的衍生子进程。

[root@CloudVM ~]# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root     24845 24843  0 22:09 pts/0    00:00:00 -bash
root     25320 24845  0 22:19 pts/0    00:00:00 ps -f
[root@CloudVM ~]# 

作为外部命令,ps命令执行时会创建出一个子进程。在这里,ps命令的PID是25320,父PID是24845。作为父进程的bash shell的PID是24845。

当进程必须执行衍生操作时,它需要花时间和精力来设置新子程序环境。所以说,外部命令多少还是有代价的。

说明 就算衍生出子进程或是创建了子shell,你仍然可以通过发送信号与其沟通,这一点无论是在命令行还是在脚本编写中都是极其有用的。发送信号(signaling)使得进程间可以通过信号进行通信。

内建命令

内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成了一体,作为shell工具的组成部分存在。不需要协助外部程序文件来运行。

exit命令内建于bash shell。可以利用type命令来了解某个命令是否是内建的。

[root@CloudVM ~]# type cd
cd is a shell builtin
[root@CloudVM ~]# 

因为即不需要通过衍生出子进程来执行,也不需要打开程序文件,内建命令的执行速度要更快,效率也更高。

要注意,有些命令有多种实现。例如echo、pwd和cd即有内建命令也有外部命令。两种实现略有不同。要查看命令的不同实现,使用type命令的-a选项。

[root@CloudVM ~]# type -a echo
echo is a shell builtin
echo is /usr/bin/echo
[root@CloudVM ~]# which echo
/usr/bin/echo
[root@CloudVM ~]# type -a pwd
pwd is a shell builtin
pwd is /usr/bin/pwd
[root@CloudVM ~]# which pwd
/usr/bin/pwd
[root@CloudVM ~]# type -a cd
cd is a shell builtin
cd is /usr/bin/cd
[root@CloudVM ~]# which cd
/usr/bin/cd
[root@CloudVM ~]# 

命令type -a 显示出了每个命令的两种实现。注意,which命令只显示出了外部命令文件。

窍门 对于有多种实现的命令,如果想要使用其外部命令实现,直接指明对应的文件就可以了。例如,要使用外部命令pwd,可以输入/usr/bin/pwd。

使用history命令

一个有用的内建命令是history命令。bash shell会跟踪你用过的命令。你可以换回这些命令并重新使用。

要查看最近使用过的命令列表,可以输入不带参数的history命令。

[root@CloudVM ~]# history
   22  ls -l
   23  man ls
   24  ls -F
   25  ls -lF
。。。
。。。
 1020  clear
 1021  history

在这个例子中,显示了最近的1000条命令。

窍门 你可以设置保存在bash历史记录中的命令数。要想实现这一点,你需要修改名为HISTSIZE的环境变量

[root@CloudVM ~]# echo $HISTSIZE
1000
[root@CloudVM ~]# 

你可以唤回并重用历史命令列表中最近的命令。这样能够节省时间和击键量。输入!!,然后按回车键就能够唤出刚刚用过的那条命令来使用。

[root@CloudVM ~]# ps --forest
  PID TTY          TIME CMD
24845 pts/0    00:00:00 bash
28343 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# !!
ps --forest
  PID TTY          TIME CMD
24845 pts/0    00:00:00 bash
28348 pts/0    00:00:00  \_ ps
[root@CloudVM ~]# 

当输入!!时,bash 首先会显示出从shell的历史记录中唤回的命令。然后执行该命令。

命令历史记录被保存在隐藏文件.bash_history中,它位于用户的主目录中。这里要注意的是,bash命令的历史记录是先存放在内存中,当shell退出时才被写入到历史文件中。

可以在退出shell会话之前强制将命令历史记录写入.bash_history文件。要实现强制写入,需要使用history命令的-a选项。

[root@CloudVM ~]# history -a
[root@CloudVM ~]# history
 1027  clear
 1028  history
 1029  cat .bash_history
 1030  history -a
 1031  history
[root@CloudVM ~]# cat .bash_history
 clear
 history
 cat .bash_history
 history -a

可见,history命令和.bash_history文件的输出时一样的,除了最近的那条history命令,因为它是在history -a命令之后出现的。

说明 如果你打开了多个终端会话,仍然可以使用history -a命令在打开的会话中向.bash_history文件中添加记录。但是对于其他打开的终端会话,历史记录不会自动更新。这是因为.bash_history文件只有在打开首个终端会话时才会被读取。要想强制重新读取.bash_history文件,更新终端会话的历史记录,可以使用history -n命令。

你可以唤回历史列表中任意一条命令。只需输入惊叹号和命令在历史列表中的编号即可。

[root@CloudVM ~]# history
   34  exit
   35  uname -an
   36  clear
[...]
   1010  type -a echo
   1011  which echo
   1012  type -a pwd
   1013  which pwd
[...]
[root@CloudVM ~]# !1010
type -a echo
echo is a shell builtin
echo is /usr/bin/echo
[root@CloudVM ~]# 

编号为1010的命令从命令历史记录中被取出。和执行最近的命令一样,bash shell首先显示出从shell历史记录中唤回的命令,然后执行该命令。

使用bash shell命令历史记录能够大大地节省时间。利用内建的history命令能够做到的事情很多,可以通过输入man history来查看history命令的bash手册页面。

命令别名

alias命令时另一个shell的内建命令。命令别名允许你为常用的命令(及其函数)创建另一个名称,从而将输入量减少到最低。

你所使用的Linux发行版很有可能已经为你设置好了一些常用命令的别名。要查看当前可用的别名,使用alias命令以及选项-p。

[root@CloudVM ~]# alias -p
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
[root@CloudVM ~]# 

注意,在该CentOS Linux发行版中,有一个别名取代了标准命令ls。它自动加入了—color选项,表明终端支持彩色模式的列表。

可以使用alias命令创建属于自己的别名。

[root@CloudVM ~]# alias li='ls -li'
[root@CloudVM ~]# li
total 44
1179851 drwxr-xr-x 2 root root  4096 Jan 13 01:40 newdir
 917518 -rw-r--r-x 1 root root     0 Jan 13 01:30 newfile
 917520 -rw-r--r-- 1 root root   172 Mar  7 22:32 oldshell.sh
 917519 -rw-r--r-- 1 root root  1887 Mar 15 00:43 printenv_output
 917522 -rw-r--r-- 1 root root 10240 Mar 16 13:48 rico.tar
 917521 -rw-r--r-- 1 root root  2755 Mar 15 00:43 set_output
1054478 drwxr-xr-x 3 root root  4096 Mar 16 13:05 testdir
 917515 -rw-r--r-- 1 root root 10240 Mar 16 13:48 testdir.tar

在定义好别名之后,你随时都可以在shell中使用它,就算在shell脚本中也没问题。要注意,因为命令别名属于内部命令,一个别名仅在它所被定义的shell进程中才有效。

[root@CloudVM ~]# alias li='ls -li'
[root@CloudVM ~]# bash
[root@CloudVM ~]# li
bash: li: command not found
[root@CloudVM ~]# exit
exit
[root@CloudVM ~]# 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值