
2.3 Bash 的基本功能
我们已经知道了Shell 是什么,也编写了一个简单的Shell 脚本,那么,Bash 还有哪些非常方便的功能呢?Bash 能够支持非常多的功能,在这里主要介绍历史命令、命令与文件补全、命令别名、常用快捷键、输入/输出重定向、多命令顺序执行、管道符、通配符和其他特殊字符。其他内容,如工作管理(前台、后台控制,参考第6章),将在其他章节中介绍。
2.3.1 历史命令
1.历史命令的查看
Bash 拥有完善的历史命令,这对于简化管理操作、排查系统错误都有重要的作用,而且使用简单、方便,建议大家多使用历史命令。系统保存的历史命令可以使用history 命令查看,命令格式如下:

如果history 命令直接回车,则可用于查看系统保存的历史命令。命令如下:

这样就可以查看刚刚输入的系统命令,而且每条命令都是有编号的。系统默认保存1000条历史命令,这是通过环境变量(环境变量的含义参见2.4.3节)HISTSIZE 来进行设置的,可以在环境变量配置文件/etc/profile 中进行修改。命令如下:

如果觉得1000条历史命令不够日常管理使用,那么是否可以增加呢?当然可以,只需修改环境变量配置文件/etc/profile 中的HISTSIZE 字段即可。不过,我们需要考虑一个问题:这些历史命令是保存在哪里的呢?如果历史命令是保存在文件中的,那么我们可以放心地增加历史命令的保存数量,因为哪怕有几万条历史命令,也不会占用多大的硬盘空间。但是,如果历史命令是保存在内存当中的,就要小心了。好在历史命令是保存在~/.bash_history 文件中的,所以我们可以放心地增加历史命令的保存数量,如10000条。命令如下:

需要注意的是,因为每个用户的历史命令是单独保存的,所以在每个用户的家目录中都有.bash_history 这个历史命令保存文件。
如果某个用户的历史命令总条数超过了历史命令的保存数量,那么新命令会变成最后一条命令,而最早的命令会被删除。假设系统只能保存1000条历史命令,而笔者已经保存了1000条历史命令,那么新输入的命令会被保存成第1000条命令,而最早的第1条命令会被删除。
还要注意,使用history 命令查看到的历史命令和~/.bash_history 文件中保存的历史命令是不同的。这是因为当前登录操作的命令并没有被直接写入~/.bash_history 文件中,而是被保存在缓存中,需要等当前用户注销之后,缓存中的命令才会被写入~/.bash_history 文件中。如果需要把缓存中的命令直接写入~/.bash_history 文件中,而不等当前用户注销后再写入,则需要使用“-w”选项。命令如下:

这时再去查看~/.bash_history 文件,查看到的历史命令就和使用history 命令查看到的历史命令一致了。
如果需要清空历史命令,则只需执行如下命令:

这样就会把缓存和~/.bash_history 文件中保存的历史命令清空。
2.历史命令的调用
如果想要使用原先的历史命令,则有以下几种方法:
● 使用上、下箭头调用以前的历史命令。
● 使用“!n”重复执行第n 条历史命令。

● 使用“!!”重复执行上一条命令。

● 使用“!字符串”重复执行最后一条以该字符串开头的命令。

● 使用“!$”重复上一条命令的最后一个参数。

2.3.2 命令与文件补全
在Bash 中,命令与文件补全是非常方便与常用的功能,只要在输入命令或文件时按Tab 键,就会自动进行补全。命令补全是按照PATH 环境变量所定义的路径查找命令的(在2.4.3节中会详细介绍),而文件补全是按照文件位置查找文件的。
比如,想要知道以user 开头的命令有多少,就可以执行以下操作:

不仅命令可以补全,文件和目录也可以用Tab 键进行补全。大家一定要养成使用Tab 键进行补全的习惯,这样不仅可以加快输入速度,而且可以减少输入错误。
2.3.3 命令别名
命令别名是什么呢?可以把它当作命令的“小名”。但是,这样做有什么意义呢?
比如,笔者刚接触Linux 系统时,使用的编辑器是Vi,但是现在Vim 的功能明显比Vi的功能更加强大,所以现在流行的编辑器变成了Vim。但是,笔者已经习惯了输入vi 命令,而不习惯输入vim 命令,别看一个小小的“m”的区别,在执行命令时总觉得别扭,这时别名就可以起作用了。只要定义vim 命令的别名为vi,这样,以后执行的vi 命令实际上就是vim 命令。


大家需要注意一点,命令别名的优先级要高于命令本身的优先级。所以,一旦给vim 命令设置了别名vi,那么原始的vi 命令就不能使用了。所以,除非确定原命令是不需要的,否则别名不能和系统命令重名。再举一个例子:

在配置和使用Apache 时,需要不断地重启Apache 服务。这时,定义“sta”为Apache 启动命令的别名,“sto”为Apache 停止命令的别名,可以有效地加快Apache 服务的重启速度。当然,笔者已经确定了系统中没有“sta”和“sto”命令(为了不和系统命令产生冲突,才起了这么别扭的别名),因此,这两个别名不会覆盖系统命令。
补充:如何确定系统中没有“sta”和“sto”命令呢?还记得whereis 和which 命令吗?
当然,使用Tab 键命令补全功能也能够确定是否有这两个命令。手工输入命令执行一下也可以确定。
既然命令别名的优先级高于命令本身的优先级,那么,在执行命令时,具体的顺序是这样的:
(1)第一顺位执行使用绝对路径或相对路径执行的命令。
(2)第二顺位执行命令别名。
(3)第三顺位执行Bash 的内部命令。
(4)第四顺位执行按照PATH 环境变量定义的目录查找,顺序找到的第一条命令。
别名就是这样简单的。不过,如果使用命令直接定义别名,那么这个别名只是临时生效的,一旦注销或重启系统,这个别名就马上消失了。为了让这个别名永久生效,可以把别名写入环境变量配置文件~/.bashrc 中。命令如下:

这样一来,这些别名就可以永久生效了。那么,环境变量配置文件又是什么呢?顾名思义,环境变量配置文件就是用来定义操作环境的,别名当然也是操作环境,在2.5.2节中将详细介绍这个文件的作用。
设定好的别名可以删除吗?只要执行unalias 命令就可以方便地删除别名。命令如下:

当然,如果确定要删除别名,则要同时删除环境变量配置文件中的相关项。
2.3.4 Bash 常用快捷键
在Bash 中有非常多的快捷键,如果可以熟练地使用这些快捷键,则可以有效地提高工作效率。只是快捷键相对较多,不太容易记忆,需要多加练习和使用。Bash 常用快捷键如表2-3所示。
表2-3 Bash 常用快捷键

在这些快捷键中,加粗的快捷键比较常用,大家要熟练使用。
2.3.5 输入/输出重定向
1.Bash 的标准输入/输出
所谓输入/输出重定向,从字面上看,就是改变输入与输出方向的意思。但是,标准的输入与输出方向是什么呢?先来解释一下输入设备和输出设备各有哪些。现在计算机的输入设备非常多,常见的有键盘、鼠标、麦克风、手写板等;常见的输出设备有显示器、投影仪、打印机等。不过,在Linux 系统中,标准输入设备指的是键盘,标准输出设备指的是显示器。
在Linux 系统中,所有的内容都是文件,计算机硬件也是文件,那么标准输入设备(键盘)和标准输出设备(显示器)当然也是文件。通过表2-4来看看这些标准输入/输出设备。
表2-4 标准输入/输出设备

Linux 是使用设备文件名来表示硬件的(比如,/dev/sda1代表第一块SATA 硬盘的第一个主分区),但是,键盘和显示器的设备文件名并不容易记忆,那么我们就用“0”“1”“2”来分别代表标准输入、标准输出和标准错误输出。
了解了标准输入和标准输出,那么,输出重定向指的是改变输出方向,不再输出到屏幕上,而输出到文件或其他设备中;输入重定向指的是不再使用键盘作为输入设备,而把文件的内容作为命令的输入。
2.输出重定向
输出重定向指的是把命令的执行结果不再输出到屏幕上,而输出到文件或其他设备中。这样做最大的好处就是把命令的执行结果保存到指定的文件中,在需要的时候可以随时调用。Bash支持的输出重定向符号如表2-5所示。
表2-5 Bash 支持的输出重定向符号

1)标准输出重定向
在输出重定向中,“>”代表的是覆盖,“>>”代表的是追加。举一个例子:


这就是“>”的作用,任何有输出的命令都可以使用输出重定向把命令的输出保存到文件中。不过,覆盖保存会把这个文件中的原有内容清空,所以追加重定向更加实用。

2)标准错误输出重定向
如果想要把命令的错误输出保存到文件中,则用标准输出重定向是不行的。比如:

因为在当前目录下没有test 文件或目录,所以“ls test”命令报错了。不过,命令的错误输出并没有保存到err.log 文件中,而输出到了屏幕上。这时需要这样来写这条命令:

2代表错误输出,只有这样,才能把命令的错误输出保存到指定的文件中。需要注意的是,在错误输出的大于号左侧一定不能有空格,否则会报错;右侧加不加空格则没有影响。但是,为了方便记忆,建议在错误输出的大于号左右两侧都不能有空格。
3)正确输出和错误输出同时保存
在实际使用中,以上两种方法都不实用,因为正确输出和错误输出的保存方法是分开的。也就是说,正确输出和错误输出的格式是不同的。要想保存命令的错误输出,需要事先知道命令会报错,才能采用错误输出保存格式,这显然是不合理的。
那么,最常用的还是可以把正确输出和错误输出都保存下来的方法。命令如下:


要想把正确输出和错误输出写入同一个文件中,有两种方法。
● “命令 >> 文件 2>&1”。
● “命令 &>>文件”。
按照个人习惯,这两种方法都可以使用。不过,我们还可以把正确输出和错误输出分别保存到不同的文件中。命令如下:

如果这样写,会把命令的正确输出写入list.log 文件中,可以当作正确日志;把命令的错误输出写入err.log 文件中,可以当作错误日志。笔者认为,如果要保存命令的执行结果,那么这种方法更加清晰。
如果我们既不想把命令的执行结果保存下来,也不想把命令的执行结果输出到屏幕上,干扰命令的执行,则可以把命令的所有执行结果放入/dev/null 中。大家可以把/dev/null 当成Linux系统的垃圾箱,任何放入垃圾箱的数据都会被丢弃,而且不能被恢复。命令如下:

3.输入重定向
既然输出重定向是改变输出的方向,把命令的输出重定向到文件中,那么输入重定向就是改变输入的方向,不再使用键盘作为命令的输入,而使用文件作为命令的输入。先介绍一个命令wc,命令格式如下:


wc 命令非常简单,可以统计通过键盘输入的数据。如果使用输入重定向符号“<”,则可以统计文件的内容。命令如下:

其实,如果直接使用“wc anaconda-ks.cfg”命令,则也是可以统计这个文件的内容的,在这里只是演示一下输入重定向的作用。那么,输入重定向在实际工作中有用吗?输入重定向确实不如输出重定向常见,但是,当需要使用patch 命令打入补丁时,就会用到输入重定向。
还有一个输入重定向符号“<<”,这个符号的作用是使用关键字作为命令输入的结束,而不使用Ctrl+D 组合键。命令如下:

“<<”之后的关键字可以自由定义,只要再碰到相同的关键字,两个关键字之间的内容将作为命令的输入(不包括关键字本身)。
2.3.6 多命令顺序执行
在Bash 中,如果需要让多条命令顺序执行,则有这样几种方法,如表2-6所示。
表2-6 多命令顺序执行的方法

1.“;”多命令顺序执行
如果使用“;”连接多条命令,那么这些命令会依次执行,但是,在各条命令之间没有任何逻辑关系。也就是说,不论哪条命令报错,后面的命令还是会依次执行。举一个例子:

这就是“;”执行符的作用,不论前一条命令是否正确执行,都不影响后续命令的执行。再举一个例子:

当需要一次执行多条命令,而这些命令之间又没有任何逻辑关系时,就可以使用“;”来连接多条命令。
2.“&&”逻辑与
如果使用“&&”连接多条命令,那么这些命令之间就有逻辑关系了。只有第一条命令正确执行了,“&&”连接的第二条命令才会执行。那么,命令2是如何知道命令1正确执行的呢?这就需要Bash 的预定义变量$?(参考2.4.5节)的支持了。如果$?的返回值是0,则证明上一条命令正确执行;如果$?的返回值是非0,则证明上一条命令报错。举一个例子:


再举一个例子。在安装源码包时,需要执行“./configure”“make”“make install”命令,但是,在安装软件时又需要等待较长时间,那么,是否可以利用“&&”同时执行这3条命令呢?当然可以,命令如下:

在这里,“\”代表一行命令没有输入结束。因为命令太长了,所以加入“\”字符,可以换行输入。利用“&&”就可以让这3条命令同时执行,然后我们就可以休息片刻,等待命令执行结束。
请大家思考:在这里是否可以把“&&”替换为“;”或“||”呢?当然是不行的,因为这3条安装命令必须在前一条命令正确执行之后,才能执行后一条命令。如果把“&&”替换为“;”,则不管前一条命令是否正确执行,后一条命令都会执行。如果把“&&”替换为“||”,则只有前一条命令报错,后一条命令才会执行。
3.“||”逻辑或
如果使用“||”连接多条命令,则只有前一条命令执行错误,后一条命令才能执行。举一个例子:

“&&”和“||”非常有意思。虽然我们暂时还没有学习if 判断语句,但是“&&”和“||”的结合使用,已经可以实现if 双分支判断语句的功能。我们来试试。
如果想要判断某条命令是否正确执行,可以这样来做:

请大家思考:判断命令是否正确执行的格式是“命令 && echo "yes" || echo "no"”,先写“&&”,后写“||”。如果反过来写,先写“||”,后写“&&”,则是否可以?我们来试试。

2.3.7 管道符
1.行提取命令grep
在讲解管道符之前,先讲解一下行提取命令grep。grep 命令的作用是在文件中提取和匹配符合条件的字符串所在的行。命令格式如下:


举一个例子:

因为grep 是行提取命令,所以只要一行数据中包含“搜索内容”,就会列出整行的数据。在这个例子中,会在/etc/passwd 文件中查找所有包含“/bin/bash”的行。而已知只有可登录用户的Shell 才是“/bin/bash”,而伪用户的Shell 是“/sbin/nologin”,所以这条命令会列出在当前系统中所有可以登录的用户。
再举几个例子:

find 也是搜索命令,那么,find 命令和grep 命令有什么区别呢?
1)find 命令
find 命令用于在系统中搜索符合条件的文件名。如果需要模糊查找,则使用通配符(参见2.3.8节)进行匹配。在搜索时,文件名是完全匹配的。完全匹配是什么意思呢?举一个例子:

完全匹配的意思就是:搜索的内容必须和原始文件一模一样,才能被搜索到。
如果想要找到abcd 文件,则必须依靠通配符,如find . -name "abc*"。
注意:find 命令是可以通过-regex 选项识别正则表达式规则的。也就是说,find 命令可以按照正则表达式规则进行匹配,而正则表达式是模糊匹配的。但是,对于初学者而言,find 和grep 命令本身就不好理解,所以在这里只按照通配符规则来进行find 查找。
2)grep 命令
grep 命令用于在文件中搜索符合条件的字符串。如果需要模糊查找,则使用正则表达式(参见3.1节)进行匹配。在搜索时,字符串是包含匹配的。
grep 命令就和find 命令不太一样了,当使用grep 命令在文件中查找符合条件的字符串时,只要搜索的内容包含在数据行中,就会列出整行内容。举一个例子:

通过这两个例子,大家就可以知道完全匹配和包含匹配的区别了。
2.管道符介绍
在Bash 中,管道符使用“|”表示。管道符也是用来连接多条命令的,如“命令1| 命令2”。不过,和多命令顺序执行不同的是,用管道符连接的命令,命令1的正确输出作为命令2的操作对象。在这里需要注意,命令1必须有正确输出,而命令2必须可以处理命令1的输出结果;而且命令2只能处理命令1的正确输出,而不能处理错误输出。
举一个例子。我们经常使用ll 命令查看文件的长格式,不过,在有些目录中文件众多,如/etc/目录,使用ll 命令显示的内容就会非常多,只能看到最后输出的内容,而不能看到前面输出的内容。这时我们马上想到,使用more 命令可以分屏显示文件内容。但是,怎么让more 命令分屏显示命令的输出呢?笔者想到了一种笨办法,命令如下:

但是,这样操作实在不方便,这时就可以利用管道符了。命令如下:

可以这样理解这条命令:先把“ll -a /etc/”命令的输出保存到某个临时文件中,再用more命令处理这个文件。也就是第一条命令的正确输出是第二条命令的操作对象。
注意:因为ll 命令操作的是文件名,所以在匹配时使用的是通配符。但是,一旦加入管道符,因为在管道符之后的内容相当于操作的是文件内容,所以在匹配时使用的是正则表达式。
关于管道符,再举几个例子:

2.3.8 通配符
在Bash 中,如果需要模糊匹配文件名或目录名,则会用到通配符。通过表2-7介绍一下常用的通配符。
表2-7 常用的通配符

举几个例子:

2.3.9 Bash 中的其他特殊字符
在Bash 中还有很多其他的特殊字符,在这一小节中集中进行说明,如表2-8所示。
表2-8 Bash 中的其他特殊字符

续表

下面举几个例子来解释一下比较常用和容易搞混的符号。
1.单引号和双引号
单引号和双引号用于当变量值出现空格时。比如,name=shen chao 这样执行就会出现问题,必须用引号括起来,如name="shen chao"。不过,引号有单引号和双引号之分,二者的主要区别在于,用单引号括起来的字符都是普通字符,就算特殊字符也不再拥有特殊含义;而在用双引号括起来的字符中,“$”“\”和反引号是拥有特殊含义的,“$”代表调用变量的值,反引号代表调用命令。还是来看例子吧。

所以,如果需要在双引号中间输出“$”和反引号,则要在这两个字符前加入转义符“\”。
2.反引号
如果需要调用命令的输出,或者把命令的输出赋予变量,则命令必须使用反引号包含,这条命令才会执行。反引号的作用和$(命令)的作用是一样的,但是反引号非常容易和单引号搞混,所以推荐使用$(命令)的方式调用命令的输出。命令如下:

还是这句话,不管是从容易混淆的角度,还是从POSIX 规范的角度来说,尽量使用$(命令)的方式来调用命令的输出,而不要使用反引号。
强调一下:只有需要调用命令的输出,或者需要把命令的输出赋予变量,才需要把命令用$()括起来。如果只需要在Shell 中执行系统命令,则直接执行即可,因为Shell 的特点就是可以直接执行Linux 系统命令。
3.小括号、中括号和大括号
中括号主要用于变量的测试,而大括号也可以用于变量的变形和替换,这两种用法在2.4节中再详细介绍。在这里主要探讨在执行一串命令时小括号和大括号的作用。
在介绍小括号和大括号的区别之前,先解释两个概念:父Shell 和子Shell。在Bash 中是可以调用新的Bash 的,比如:

这时,可以通过pstree 命令查看一下进程数,命令如下:

可以看到,命令都是通过ssh 远程服务连接的,在ssh 中生成了第一个Bash,这个Bash就是父Shell。因为刚刚执行了bash 命令,所以在第一个Bash 中生成了第二个Bash,这个Bash就是子Shell,我们是在子Shell 中运行pstree 命令的。
关于父Shell 和子Shell,可以想象成在Windows 系统中开启了一个“cmd”字符操作终端,那么Windows 系统本身就是父Shell,而“cmd”字符操作终端则是子Shell;也可以理解为在一个操作界面中又开启了一个子操作界面。
知道了父Shell 和子Shell,我们接着解释小括号和大括号的区别。如果用于一串命令的执行,那么小括号和大括号的主要区别在于:
● ()在执行一串命令时,需要重新开启一个子Shell 来执行。
● {}在执行一串命令时,是在当前Shell 中执行的。
● ()里的最后一条命令后面可以不用分号。
● {}里的最后一条命令后面要用分号。
● {}里的第一条命令和左括号之间必须有一个空格。
● ()里的各条命令和括号之间不必有空格。
在执行一串命令时,小括号和大括号的共同点如下:
● ()和{}都是把一串命令放在括号里面的,并且命令之间用“;”隔开。
● ()和{}里面的某条命令的重定向只影响该命令,但括号外的重定向则会影响到括号里的所有命令。
还是举几个例子来看看吧,因为这样写实在太抽象了。

其实,在执行一串命令时,如果使用的是小括号,则这串命令所做的修改只在子Shell 中生效,一旦命令执行结束,回到父Shell 中,修改就会失效;而如果使用的是大括号,则这串命令直接在父Shell 中执行,当命令执行结束后,修改依然会生效。