理解shell

shell不单单是CLI,而是一种复杂的交互式程序。

1. shell的类型

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

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

[root@Wesuiliye ~]# tail -n 1 /etc/passwd
wesuiliye:x:1001:1001::/home/wesuiliye:/bin/bash

在现代Linux系统中,bash shell程序(bash)通常位于/usr/bin目录。不过,在你的Linux系统中,也有可能位于/bin目录。which bash命令可以帮助我们找出bash shell的位置:

[root@Wesuiliye ~]# which bash
/usr/bin/bash

长列表中文件名尾部的星号(*)表明bash文件(bash shell)是一个可执行程序。

[root@Wesuiliye ~]# ls -lF /usr/bin/bash
-rwxr-xr-x 1 root root 964536 Nov 25 2021 /usr/bin/bash*

注意 在现代Linux系统中,/bin目录通常是/usr/bin/目录的符号链接,这就是为什么用户wesuiliye的默认shell程序是/bin/bash,但bash shell程序实际位于/usr/bin/目录。

在基于Debian的Linux系统(比如Ubuntu)中经常会碰到dash,这是Ash shell的另一个版本。

在大多数Linux系统中,/etc/shells文件中列出了各种已安装的shell,这些shell可以作为用户的默认shell。

[root@Wesuiliye ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/bin/tcsh #源自最初的C shell:
/bin/csh #C shell是指向tcsh shell的软链接
/bin/dash

注意 在很多Linux发行版中,你会发现shell文件似乎存在于两个位置:/bin和/usr/bin。这是因为在现代Linux系统中,/bin是指向/usr/bin的符号链接。

用户可以将这些shell程序中的某一个作为自己的默认shell。不过由于bash shell的广为流行,很少有人使用其他的shell作为默认的交互式shell。默认的交互式shell(default interactive shell)也称登录shell(login shell),只要用户登录某个虚拟控制台终端或是在GUI中启动终端仿真器,该shell就会启动。

作为默认的系统shell(default system shell),sh(/bin/sh)用于那些需要在启动时使用的系统shell脚本。

你经常会看到某些发行版使用软链接将默认的系统shell指向bash shell,比如CentOS发行版

[root@Wesuiliye ~]# which sh
/usr/bin/sh
[root@Wesuiliye ~]# ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 Jan 9 20:52 /usr/bin/sh -> bash

但要注意,在有些发行版中,默认的系统shell并不指向bash shell,比如Ubuntu发行版:

$ which sh
/usr/bin/sh
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 Mar 10 18:43 /usr/bin/sh -> dash
$

在这里,默认的系统shell(/usr/bin/sh)指向的是Dash shell

提示 对bash shell脚本来说,这两种shell(默认的交互shell和默认的系统shell)可能会导致问题。

并不是非得使用默认的交互式shell。可以启动任意一种已安装的shell,只需输入其名称即可。但屏幕上不会有任何提示或消息表明你当前使用的是哪种shell。$0变量可以助你一臂之力。命令echo $0会显示当前shell的名称,提供必要的参考。

注意 使用echo $0显示当前所用shell的做法仅限在shell命令行中使用。如果在shell脚本中使用,则显示的是该脚本的名称。

有了方便的$0变量,就能知道当前使用的shell了。输入命令dash,启动Dash shell,通过echo $0显示新的shell。

[root@Wesuiliye ~]# echo $0
-bash
[root@Wesuiliye ~]# dash
# echo $0
dash

注意 在上面的例子中,注意第一个echo $0命令的输出:bash之前有一个连字符(-)。这表明该shell是用户的登录shell。

$是Dash shell的CLI提示符。输入命令exit就可以退出Dash shell程序(对于bash shell也是如此):

[root@Wesuiliye ~]# dash
# echo $0
dash
# exit
[root@Wesuiliye ~]# echo $0
-bash

2. shell的父子关系

用户登录某个虚拟控制台终端或在GUI中运行终端仿真器时所启动的默认的交互式shel(登录shell)是一个父shell。到目前为止,都是由父shell提供CLI提示符并等待命令输入。

当你在CLI提示符处输入bash命令(或是其他shell程序名)时,会创建新的shell程序。这是一个子shell。子shell也拥有CLI提示符,同样会等待命令输入。

在输入bash并生成子shell时,屏幕上不会显示任何相关信息,要想搞清楚来龙去脉,需要用到ps命令。在生成子shell的前后配合 -f选项来使用:

[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30426 29987 0 14:52 pts/0 00:00:00 ps -f
[root@Wesuiliye ~]# bash
[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30442 29987 1 14:52 pts/0 00:00:00 bash
root 30477 30442 0 14:52 pts/0 00:00:00 ps -f

第一次使用ps -f的时候,显示出了两个进程。一个进程的PID是29987(第二列),运行的是bash shell程序(最后一列)。另一个进程(PID为30426)是实际运行的ps -f命令。

注意 进程就是正在运行的程序。bash shell是一个程序,当它运行的时候,就成了进程。一个运行着的shell同样是进程。因此,在说到运行bash shell的时候,经常会看到“shell”和“进程”这两个词交换使用。

输入命令bash之后,就创建了一个子shell。第二个ps -f是在子shell中执行的。你可以从显示结果中看到有两个 bash shell程序在运行。一个是父shell进程,其PID为29987。另一个是子shell进程,其PID为30442。注意,子shell的父进程ID(PPID)是29987,表明这个进程就是该子shell的父进程。

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

子shell既可以从父shell中创建,也可以从另一个子shell中创建:

[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30508 29987 0 14:55 pts/0 00:00:00 ps -f
[root@Wesuiliye ~]# bash
[root@Wesuiliye ~]# bash
[root@Wesuiliye ~]# bash
[root@Wesuiliye ~]# ps --forest
PID TTY TIME CMD
29987 pts/0 00:00:00 bash
30509 pts/0 00:00:00 \_ bash
30544 pts/0 00:00:00 \_ bash
30574 pts/0 00:00:00 \_ bash
30614 pts/0 00:00:00 \_ ps

在上面的例子中,bash命令被输入了3次。这实际上创建了3个子shell。

ps --forest命令展示了这些子shell间的嵌套结构。

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

[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30509 29987 0 14:55 pts/0 00:00:00 bash
root 30544 30509 0 14:55 pts/0 00:00:00 bash
root 30574 30544 0 14:55 pts/0 00:00:00 bash
root 30639 30574 0 14:56 pts/0 00:00:00 ps -f

bash shell程序可以使用命令行选项来修改shell的启动方式。

提示 如果想查看bash shell的版本号,在命令行中出bash --version即可。该命令不会创建子shell,只会显示系统中GNU bash shell程序的当前版本。

可以使用exit命令有条不紊地退出子shell:

[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30509 29987 0 14:55 pts/0 00:00:00 bash
root 30544 30509 0 14:55 pts/0 00:00:00 bash
root 30574 30544 0 14:55 pts/0 00:00:00 bash
root 30655 30574 0 14:57 pts/0 00:00:00 ps -f
[root@Wesuiliye ~]# exit
exit
[root@Wesuiliye ~]# exit
exit
[root@Wesuiliye ~]# exit
exit
[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 30656 29987 0 14:58 pts/0 00:00:00 ps -f

exit命令不仅能够退出子shell,还可以注销(log out)当前的虚拟控制台终端或终端仿真器软件。只需在父shell中输入exit,就能从容退出CLI了。

2.1 查看进程列表

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

[root@Wesuiliye ~]# pwd ; ls test* ; cd /etc ; pwd
/root
test_1 test_2 test_2.gz test2.gz test.gz test.gz~
/etc

在上面的例子中,所有命令依次执行,没有任何问题。不过这并不是进程列表。要想成为进程列表,命令列表必须将命令放入圆括号内

[root@Wesuiliye etc]# (pwd ; ls test* ; cd /etc ; pwd)
/etc
ls: cannot access test*: No such file or directory
/etc

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

注意 进程列表是命令分组(command grouping)的一种。另一种命令分组是将命令放入花括号内,并在命令列表尾部以分号(;)作结。语法为:{ command; }。“{” 后面需要有空格,“}” 前可以不需要空格 但是最后一条命令需要结束后也需要加“;” 。使用花括号进行命令分组并不会像进程列表那样创建子shell

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

[root@Wesuiliye etc]# pwd ; ls test* ; cd /etc ; pwd ; echo $BASH_SUBSHELL
/etc
ls: cannot access test*: No such file or directory
/etc
0

在输出结果的最后是数字0。这表明并未创建子shell来执行这些命令。

花括号命令分组

[root@Wesuiliye etc]# { pwd; echo $BASH_SUBSHELL; }
/etc
0

可以看到echo $BASH_SUBSHELL 输出的为 0,说明命令分组 { ;;} 并没有创建子shell 执行命令。

如果改用进程列表,则结果就不一样了。在列表最后加入

echo $BASH_SUBSHELL :

[root@Wesuiliye etc]# (pwd ; ls test* ; cd /etc ; pwd ; echo $BASH_SUBSHELL)
/etc
ls: cannot access test*: No such file or directory
/etc
1

这次在输出结果的最后是数字1。这表明的确创建了子shell来执行这些命令。

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

[root@Wesuiliye etc]# (pwd;echo $BASH_SUBSHELL)
/etc
1
[root@Wesuiliye etc]# (pwd;(echo $BASH_SUBSHELL))
/etc
2

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

子shell在shell脚本中经常用于多进程处理。但是,创建子shell的成本不菲(意思是要消耗更多的资源,比如内存和处理能力),会明显拖慢任务进度。在交互式CLI shell会话中,子shell同样存在问题,它并非真正的多进程处理,原因在于终端与子shell的I/O绑定在了一起。

2.2 别出心裁的子shell用法

在交互式shell CLI中,还有很多更富有成效的子shell用法。

进程列表、协程和管道都用到了子shell,各自都可以有效运用于交互式shell。

在交互式shell中,一种高效的子shell用法是后台模式。在讨论如何配合使用后台模式和子shell之前,需要先搞明白什么是后台模式。

1. 探究后台模式

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

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

[root@Wesuiliye etc]# sleep 10
[root@Wesuiliye etc]#

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

[root@Wesuiliye etc]# sleep 3000&
[1] 31182
[root@Wesuiliye etc]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 31182 29987 0 15:23 pts/0 00:00:00 sleep 3000
root 31192 29987 0 15:23 pts/0 00:00:00 ps -f

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

ps命令可以显示各种进程。注意进程列表中的sleep 3000命令。在其第二列显示的PID和该命令进入后台时所显示的PID是一样的,都是31182。

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

[root@Wesuiliye etc]# jobs
[1]+ Running sleep 3000 &

jobs命令会在方括号中显示作业号(1)。

除此之外,还有作业的当前状态(Running)以及对应的命令(sleep 3000 &)。

利用jobs命令的 -l(小写字母l)选项,还可以看到更多的相关信息。除了默认信息,-l选项还会显示命令的PID。

[root@Wesuiliye etc]# jobs -l
[1]+ 31182 Running sleep 3000 &

提示 如果运行多个后台进程,则还有一些额外信息可以显示哪个后台作业是最近启动的。在jobs命令的显示中,最近启动的作业在其作业号之后会有一个加号(+),在它之前启动的进程(the second newest process)则以减号(-)表示。

[root@Wesuiliye etc]# sleep 10&
[2] 31245
[root@Wesuiliye etc]# jobs
[1]- Running sleep 3000 &
[2]+ Running sleep 10 &

一旦后台作业完成,就会显示出结束状态(Done):

[root@Wesuiliye etc]# jobs
[1]- Running sleep 3000 &
[2]+ Done sleep 10

2. 将进程列表置入后台

通过将进程列表置入后台,可以在子shell中进行大量的多进程处理。由此带来的一个好处是终端不再和子shell的I/O绑定在一起。

之前说过,进程列表是子shell中运行的一系列命令。在进程列表中加入sleep命令并显示BASH_SUBSHELL变量,结果不出所料:

[root@Wesuiliye etc]# (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)
1
[root@Wesuiliye etc]#

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

将同样的进程列表置入后台会产生些许不同的命令输出:

[root@Wesuiliye etc]# (sleep 2 ; echo $BASH_SUBSHELL ; sleep 2)&
[2] 31306
[root@Wesuiliye etc]# 1 [2]+ Done ( sleep 2; echo $BASH_SUBSHELL; sleep 2 )
[root@Wesuiliye etc]#

进程列表置入后台会产生一个作业号和进程ID,然后会返回提示符。不过,奇怪的是表明一级子shell(single-level subshell)的数字1竟然先出现在了提示符的右边。别慌,按一下Enter键,就会得到另一个提示符了。

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

3. 协程

协程同时做两件事:一是在后台生成一个子shell,二是在该子shell中执行命令。

要进行协程处理,可以结合使用coproc命令以及要在子shell中执行的命令:

[root@Wesuiliye ~]# coproc sleep 10
[1] 31473

除了会创建子shell,协程基本上就是将命令置入后台。当输入coproc命令及其参数之后,你会发现后台启用了一个作业。屏幕上会显示该后台作业号(1)以及进程ID(31473)。

jobs命令可以显示协程的状态:

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

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

[root@Wesuiliye ~]# coproc My_job { sleep 10; }
[1] 31494
[root@Wesuiliye ~]# jobs
[1]+ Running coproc My_job { sleep 10; } &

使用扩展语法,协程名被设置成了My_Job。

这里要注意,扩展语法写起来有点儿小麻烦。你必须确保在左花括号({)和内部命令名之间有一个空格。还必须保证内部命令以分号(;)结尾。另外,分号和右花括号(})之间也得有一个空格。

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

你可以发挥才智,将协程与进程列表结合起来创建嵌套子shell。只需将命令coproc放在进程列表之前即可:

[root@Wesuiliye ~]# coproc (sleep 10; sleep 2)
[1] 31559
[root@Wesuiliye ~]# jobs
[1]+ Running coproc COPROC ( sleep 10; sleep 2 ) &
[root@Wesuiliye ~]# ps --forest
PID TTY TIME CMD
29987 pts/0 00:00:00 bash
31559 pts/0 00:00:00 \_ bash
31560 pts/0 00:00:00 | \_ sleep
31561 pts/0 00:00:00 \_ ps

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

3. 理解外部命令和内建命令

3.1 外部命令

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

ps命令就是一个外部命令。可以使用which命令和type命令找到其对应的文件名:

[root@Wesuiliye ~]# which ps
/usr/bin/ps
[root@Wesuiliye ~]# type ps
ps is hashed (/usr/bin/ps)
[root@Wesuiliye ~]# ls -l /usr/bin/ps
-rwxr-xr-x. 1 root root 100112 Oct 1 2020 /usr/bin/ps

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

[root@Wesuiliye ~]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 29987 29981 0 14:35 pts/0 00:00:00 -bash
root 31618 29987 0 15:45 pts/0 00:00:00 ps -f

作为外部命令,ps命令在执行时会产生一个子进程。在这里,ps命令的PID是31618,父PID是29987。作为父进程的bash shell的PID是29987。

只要涉及进程衍生,就需要耗费时间和资源来设置新子进程的环境。因此,外部命令系统开销较高。

注意 无论是衍生出子进程还是创建了子shell,都仍然可以通过信号与其互通,这一点无论是在使用命令行还是在编写脚本时都极其有用。进程间以发送信号的方式彼此通信。

在使用内建命令时,不需要衍生子进程。因此,内建命令的系统开销较低。=

3.2 内建命令

与外部命令不同,内建命令无须使用子进程来执行。内建命令已经和shell编译成一体,作为shell的组成部分存在,无须借助外部程序文件来执行。

cd命令和exit命令都内建于bash shell。可以使用type命令来判断某个命令是否为内建:

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

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

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

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

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

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

1. 使用history命令

bash shell会跟踪你最近使用过的命令。你可以重新唤回这些命令,甚至加以重用。history是一个实用的内建命令,能帮助你管理先前执行过的命令。

要查看最近用过的命令列表,可以使用不带任何选项的history命令:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# history
1 top
2 history

史记录中通常保存最近的1000条命令。

提示 你可以设置保存在bash历史记录中的命令数量。为此,需要修改名为HISTSIZE的环境变量

你可以唤回并重用历史列表中最近的命令。这样既能节省时间,又能少敲点儿键盘。输入 !!,然后按Enter键,唤回并重用最近那条命令:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# ps --forest
PID TTY TIME CMD
22666 pts/0 00:00:00 bash
22687 pts/0 00:00:00 \_ ps
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# !!
ps --forest
PID TTY TIME CMD
22666 pts/0 00:00:00 bash
22688 pts/0 00:00:00 \_ ps
[root@iZf8z65b5wvf6yju3zmpf7Z ~]#

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

命令历史记录被保存在位于用户主目录的隐藏文件.bash_history之中:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# pwd
/root
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# ls .bash_history
.bash_history

这里要注意的是,在CLI会话期间,bash命令的历史记录被保存在内存中。当shell退出时才被写入历史文件:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# history
1 top
2 history
3 ps --forest
4 ls
5 la -a
6 ls -a
7 pwd
8 ls -a
9 ls .bash_history
10 history
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# cat .bash_history
top

注意,history命令运行时所显示的最近命令与.bash_history文件中最后的命令并不一致。有9个已经执行过的命令并没有被记录在历史文件中。

可以在不退出shell的情况下强制将命令历史记录写入.bash_history文件。为此,需要使用history命令的 -a选项:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# history -a
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# history
1 top
2 history
3 ps --forest
4 ls
5 la -a
6 ls -a
7 pwd
8 ls -a
9 ls .bash_history
10 history
11 cat .bash_history
12 history -a
13 history
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# cat .bash_history
top
history
ps --forest
ls
la -a
ls -a
pwd
ls -a
ls .bash_history
history
cat .bash_history
history -a

注意,history命令的输出与.bash_history文件内容现在是一样的,除了最近的那条history命令,因为它是在history -a命令之后出现的。

注意 如果打开了多个终端会话,则仍然可以使用history -a命令在每个打开的会话中向 .bash_history文件添加记录。但是历史记录并不会在其他打开的终端会话中自动更新。这是因为.bash_history文件只在首次启动终端会话的时候才会被读取。要想强制重新读取.bash_history文件,更新内存中的终端会话历史记录,可以使用history -n命令。

你可以唤回历史记录中的任意命令。只需输入惊叹号和命令在历史记录中的编号即可:

[root@iZf8z65b5wvf6yju3zmpf7Z ~]# history
1 top
2 history
3 ps --forest
4 ls
5 la -a
6 ls -a
7 pwd
[root@iZf8z65b5wvf6yju3zmpf7Z ~]# !7
pwd
/root

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

提示 如果需要清除命令历史,很简单,输入history -c即可。接下来再入history-a,清除 .bash_history文件。

2. 使用命令别名

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

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

[root@Wesuiliye ~]# 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'

注意,在该Centos Linux发行版中,有一个别名取代了标准命令ls。该别名中加入了 --color=auto选项,以便在终端支持彩色显示的情况下,ls命令可以使用色彩编码(比如,使用蓝色表示目录)。LS_COLORS环境变量控制着所用的色彩编码。

提示 如果经常跳转于不同的发行版,那么在使用色彩编码来分辨某个名称究竟是目录还是文件时,一定要小心。因为色彩编码并未实现标准化,所以最好使用ls -F来判断文件类型。

可以使用alias命令创建自己的别名:

[root@Wesuiliye ~]# alias li='ls -i'
[root@Wesuiliye ~]# li
404996 aaa.zip 394092 abc.tar 535855 New_dir 394139 test_2.gz
394087 ab1c.tar 404973 a.tar 394131 num 397590 test2.gz
794761 abc 405977 b.tar 394132 test_1 397597 test.gz
397629 abcd.tar.gz 394130 file3 394136 test_2 397598 test.gz~

定义好别名之后,就可以随时在shell或者shell脚本中使用了。要注意,因为命令别名属于内建命令,所以别名仅在其被定义的shell进程中才有效。

[root@Wesuiliye ~]# alias li='ls -i'
[root@Wesuiliye ~]# bash
[root@Wesuiliye ~]# li
bash: li: command not found...
[root@Wesuiliye ~]# exit
exit
[root@Wesuiliye ~]# li
404996 aaa.zip 394092 abc.tar 535855 New_dir 394139 test_2.gz
394087 ab1c.tar 404973 a.tar 394131 num 397590 test2.gz
794761 abc 405977 b.tar 394132 test_1 397597 test.gz
397629 abcd.tar.gz 394130 file3 394136 test_2 397598 test.gz~

提示 如果需要,可以在命令行中入unalias alias-name删除指定的别名。记住,如果被删除的别名不是你设置的,那么等下次重新登录系统的时候,该别名就会再次出现。可以通过修改环境文件永久地删除某个别名。

理解shell的更多相关文章

  1. 《Linux命令行与shell脚本编程大全》 第五章理解shell

    5.1 1. cat /etc/passwd 可以查看每个用户自己的默认的shell程序. 2.默认的交互shell会在用户登录某个虚拟控制台终端时启动. 不过还有另外一个默认的shell是/bin/ ...

  2. 理解 Shell

    理解 Shell shell 的父子关系 用于登录的某个虚拟控制器终端,或在 GUI 中运行终端仿真器时所启动的默认的交互 shell,是一个父 shell.本书到目前为止都是父 shell 提供 C ...

  3. 轻松学习Linux之理解Shell的硬链接与软连接

     大家在学习linux的过程中常常遇到一些模糊且容易混淆的概念比如什么是硬链接和软链接,他们有什么区别?  软连接有点象windows中的快捷方式,连接和目标文件具有相同的节点,而硬连接就好象重新复制 ...

  4. 《Linux命令行与shell脚本编程大全》- 读书笔记3 - 理解shell

    当用户登录终端的时候,通常会启动一个默认的交互式shell.系统究竟启动哪个shell,这取决于用户配置.一般这个shell都是/bin/shell.默认的系统shell(/bin/sh)用于系统sh ...

  5. 理解shell的eval命令

    看以下两条命令:[zhangsan@XEN /sys]$ a="ls";b="\$a";c="$b";"$c"Hey! ...

  6. 一张图理解shell内核

    #!/bin/bash name="liu de hua";#name后面=不能有空格 echo "hello word ${name}work $name"; ...

  7. 理解shell中的atime,mtime,ctime

    所有文件都有3个时间信息,保存在文件系统中 atime (Access time)是文件最后一此读的时间 或者执行文件的时间 mtime (Modified time)是文件最后一次写的时间(是在写入 ...

  8. shell浅谈之九子shell与进程处理

    转自:http://blog.csdn.net/taiyang1987912/article/details/39529291 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...

  9. shell面试题目总结

    1.如何理解shell脚本中第一行#!/bin/sh #!为特殊的表示符,其后是解释此脚本的shell的路径.此脚本使用/bin/sh进行解释执行. 2.如何向脚本传递参数. 脚本名字 参数1 参数2 ...

  10. Linux 下 Shell 命令的分类及用法

    当你打算真正操纵好你的 Linux 系统,没有什么能比命令行界面更让你做到这一点.为了成为一个 Linux 高手,你必须能够理解 Shell命令的不同类型,并且会在终端下正确的使用它们. 在 Linu ...

随机推荐

  1. golang 并发问题

    如何使用channel实现定时器? 使用channel的阻塞,里面放一个sleep就可以了 Go语言--goroutine并发模型: 视频地址: https://www.bilibili.com/vi ...

  2. WEB服务与NGINX(17)- https协议及使用nginx实现https功能

    目录 1. https协议及使用nginx实现https功能 1.1 https协议概述 1.2 TLS/SSL协议原理 1.3 https的实现原理 1.4 使用openssl申请证书 1.5 ng ...

  3. VueJs创建网易音乐播放器和vueJs常见错误处理

    学习vuejs之后,总得自己上手写一些完整的应用,才能够更加了解各个结构与组件之间的运用. 下面从最基础的准备工作开始: 用vue-cli搭建vue应用:先确保自己已经安装Node环境. (1)Win ...

  4. ༺$Musique$༻

    往期链接在文末 最近好喜欢听一些有年代感的歌啊. ~~头图~~ <$ On\ \And \ On $> Hold me close til I get up Time is barely ...

  5. 精准管控|AIRIOT数字油库智能化解决方案

      在油库管理的过程中,储油罐区普遍存在分布空间范围广.安全防爆要求高.监控点多.布线复杂.自动化系统集成难度大等问题,传统的油库管理手段相对落后.管理环境复杂,企业在监测监控.设备设施管理.日常运行 ...

  6. 可以把erp当做一个分支-找自己的方向

    之前一直在寻思自己应该做哪些方面,对所有编程的问题都在研究,又看到自己研究不透.现在,某一时刻看到,可以把erp当做一个分支. 就像游戏里的天赋点一样,进入这个分支... 这是一个专一的方面,和编程普 ...

  7. win10激活方法

    slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX slmgr /skms zh.us.to slmgr /ato

  8. 开源项目分享:ChatGPT 控制台聊天应用

    开源项目分享:ChatGPT 控制台聊天应用 分享一个我最近完成的一个小应用,一个ChatGPT 的控制台聊天应用,大家都在搞AI,我也来玩一玩,顺便分享到社区,有兴趣的小伙伴可以去我的github主 ...

  9. 分布式任务调度内的 MySQL 分页查询优化

    作者:vivo 互联网数据库团队- Qiu Xinbo 本文主要通过图示介绍了用主键进行分片查询的过程,介绍了主键分页查询存在SQL性能问题,如何去创建高效的索引去优化主键分页查询的SQL性能问题.对 ...

  10. 【C#】爬取百度贴吧帖子 通过贴吧名和搜索关键词

    背景:最近喜欢看百度贴吧,因为其内容大多都是吧友的真实想法表达等等原因.但是通过网页去浏览贴吧,始终觉得不够简介,浏览帖子的效率不高,自己就萌发了通过自己爬取贴吧感兴趣的关键字内容,自己写了个winf ...