Shell 元字符

在 Shell 中,许多非字母、数字字符都拥有特殊的含义,这些字符被称为元字符。其中有几个元字符用于文件名扩展,也称为通配符。先来了解一下 Shell 中的元字符都有那些。

字符 含义
~ 通配符:home 目录
? 通配符:匹配任意一个字符
* 通配符:匹配 0 个或多个字符
[] 通配符:匹配一组字符中的任意一个字符
{} 通配符:匹配一组字符中的任意一个字符串或识别变量的边界
; 在同一行中分隔多条命令
# 注释
& 在后台运行命令
$ 引用变量
\ 转义符
' 取消所有替换(强引用)
" 取消部分替换(弱引用)
` 命令替换
! 历史列表:事件标记
< 重定向输入
> 重定向输出
| 管道
() 在子 Shell 中运行命令

通配符

通配符实际上是一种 Shell 实现的路径扩展功能。当命令中包含通配符时,Shell 会先解释通配符的意义,然后在将命令重组,最后运行该命令。

字符 含义 实例
* 匹配 0 或多个字符 a*b 匹配 aabcb、axyzb、a012b、ab ...
? 匹配任意一个字符 a?b 匹配 aab、abb、acb、a0b ...
[list] 匹配 list 中的任意单一字符 a[xyz]b 匹配 axb、ayb、azb
[!list] 匹配除 list 中的任意单一字符 a[!0-9]b 匹配 axb、aab、a-b ...
[a-f] 匹配 a~f 中的任意单一字符 a[a-f]b 匹配 aab、abb、acb ...
{abc, ...} 匹配大括号内的任意一个字符串 a{abc,xyz,123}b 匹配 aabcb、axyzb、a123b

提示

通配符看起来有点像正则表达式语句,但是它与正则表达式不同,不能相互混淆。把通配符理解为 Shell 特殊字符就行。而且涉及的只有 * ? [] {} 这几种。

在使用 []{} 时,字符中间不能有空格。

通配符可以用在所有的 Shell 命令中,组合起来可以起到意想不到的作用。

# 一次性创建多个文件
[Linux]$ touch number{1,2,3,4}.txt
[Linux]$ ls
number1.txt  number2.txt  number3.txt  number4.txt

[Linux]$ touch number{1..9}.txt
[Linux]$ ls
number1.txt  number3.txt  number5.txt  number7.txt  number9.txt
number2.txt  number4.txt  number6.txt  number8.txt


# 在使用命令前可以先使用 echo 命令验证结果
[Linux]$ echo {10..23}
10 11 12 13 14 15 16 17 18 19 20 21 22 23

[Linux]$ echo {0..3}{Z..X}
0Z 0Y 0X 1Z 1Y 1X 2Z 2Y 2X 3Z 3Y 3X

[Linux]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b

命令分隔符

在大多数情况下,一条命令行只需输入一条命令。可以用 ; 将两条命令连接起来,从而在一条命令行中输入多个命令。在输入时 ; 两边可以不加空格也可以加空格,为了方便阅读可以在 ; 后边加入一个空格。

[Linux]$ date; cal
Wed 19 Aug 2020 09:44:55 PM CST
    August 2020
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31

[Linux]$ whoami; pwd; date
glenn
/home/glenn/Public
Wed 19 Aug 2020 09:48:32 PM CST

提示

还有两个特殊的命令分隔符,即 &&|| ,也叫做条件执行。 && 前的命令执行成功后会继续执行后面的命令,俗称和命令。 || 前的命令执行失败后才会执行后面的命令,俗称或命令。

如果想在命令没有运行成功时,追加一条警告信息,可以使用以下命令:

[Linux]# update || echo 'The update program failed.'

注释

注释在命令行中基本不会用到,主要应用在脚本中。注释可以出现在脚本的任意位置,在每一行中 # 字符之后的内容都会被注释掉(在脚本执行时,会忽略所有的注释)。

# 以下两种注释相等,一般注释会单独占用一行

# 显示时间及内核
[Linux]$ date; uname
Wed 19 Aug 2020 09:58:54 PM CST
Linux

[Linux]$ date; uname # 显示时间及内核
Wed 19 Aug 2020 09:58:54 PM CST
Linux

后台运行程序

在单独的 Shell 程序中,也有前后台之分。前台即实时显示的内容,后台类似于图形界面中最小化的程序。将程序(或脚本)放入后台有两种方法:

元字符 & 将程序放入后台执行

在输入命令时在后边加入 & 符号会把命令程序直接放到后台执行,此时可以用 jobs 命令 查看后台执行程序的列表。

# 将 tar 命令放入后台执行
[Linux]$ tar -cvf web.tar /var/www/html/ &

注意

在后台运行命令时,有输出的命令(如:ping)一样会将结果输出到屏幕,所以最好将输出重定向到某个文件(如: ping www.baidu.com >out.file 2>&1 )。

使用 <Ctrl+Z> 快捷键暂停程序运行

使用 <Ctrl+Z> 快捷键放入后台的命令处于暂停状态,是不会运行的。

[Linux]$ top

# 使用 <Ctrl+Z> 快捷键可以将 top 放入后台
[Linux]$ jobs
[1]-  Stopped                 vi
[2]+  Stopped                 top

提示

bg 命令和 fg 命令

在后台暂停的命令,可以使用 bg 命令让程序在后台继续执行。格式如下:

[Linux]$ bg %1

而 fg 命令用于把后台工作恢复到前台执行,格式如下:

[Linux]$ fg %1

在使用 bg 和 fg 命令时, %1 为后台的编号( % 可以省略)。当命令不带参数执行时会对应带有 + 号的后台程序。

引用变量

使用一个定义过的变量,只要在变量名前面加 $ 符号即可,如:

[Linux]$ your_name=glenn
[Linux]$ echo $your_name
glenn
[Linux]$ echo ${your_name}
glenn

变量名外面的花括号是可选的,加不加都行。加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

[Linux]$ name=Java
[Linux]$ echo ${name}Script

如果不给 name 变量加花括号,写成 echo $nameScript ,解释器就会把 $nameScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

推荐给所有变量加上花括号,这是个好的编程习惯。

已定义的变量,可以被重新赋值,如:

[Linux]$ your_name=glenn
[Linux]$ echo $your_name
glenn
[Linux]$ your_name=rose
[Linux]$ echo $your_name
rose

注意

给变量赋值时不能写成 $your_name=rose ,只有使用变量的时候才加 $ 符。

转义符

有时候,可能希望按字面上的含义使用元字符,而不使用其特殊含义。例如,将分号作为分号使用,而不是一个命令分隔符。在这种情况下就需要用转义符去转义元字符。 Shell 中有三种转义符。

字符 说明
\(反斜杠) 转义符,去除紧跟其后的元字符的特殊意义
'(单引号) 强引用,所有的元字符都使用其字面含义。强引用中不允许再次出现单引号
"(双引号) 弱引用,只保留 $`\ 三种元字符的特殊含义
# 转义符转义
[Linux]$ echo It is warm and sunny\; come over and visit
It is warm and sunny; come over and visit

# 中间有空格的文件名使用引用
[Linux]$ cd 'your name'
[Linux]$ cd "your name"

提示

  • 使用转义符转义单个字符
  • 使用强引用引用字符串
  • 使用弱引用引用字符串,保留 $`\ 三种元字符的特殊含义。

转义字符有强弱之分, \ 大于 ' 大于 "

命令替换

命令替换允许在一条命令中嵌入另一条命令,Shell 首先执行嵌入的命令,并且用输出替换该命令,然后在执行整条命令。通过将一条命令封装在 ` 中,可以将它嵌入另一条命令。

[Linux]$ echo "The time and date are `date`"
The time and date are Fri 21 Aug 2020 09:29:52 PM CST

提示

命令替换还有另外一种格式,即将命令放入 $() 中, $(command) 等同于 `command`。注意区分 ${} 是引用变量。

历史列表

Shell 通过使用 ! 字符,为历史列表提供了一个特殊的扩展功能。例如用 !24 重新执行历史列表中事件编号为 24 的命令。

Shell 中还有几个好用的历史列表命令:

字符 说明
!! 执行上一条命令(等同于 <Up> + <Enter> 键)
!number 执行历史列表中第 number 行的命令
!string 执行最近的以 string 字符串开头的命令
!?string 执行最近的包含这个字符串的命令
!* 使用上一条命令的选项和参数
!$ 使用上一条命令的最后一个参数

提示

应该谨慎地使用 !string!?string 格式,除非你完全确信历史列表条目的内容。

[Linux]$ ls -l Music/
total 0
[Linux]$ !!
ls -l Music/
total 0
[Linux]$ ls !*
ls -l Music/
total 0
[Linux]$ ls !$
ls Music/

重定向

在 Shell 中,标准输入/标准输出的概念很好理解。默认情况下,大多数程序从键盘读取输入,并将输出写入到屏幕。标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕。

在 Linux 进程中,每个输入源和每个输出目标都会有一个唯一的数字标识,这个数字称为文件描述符。例如,一个进程可以从文件 #8 中读取数据,并将数据写入到文件 #6 中。默认情况下,Linux 为每个进程提供 3 个预定义的文件描述符,即 0 代表标准输入, 1 代表标准输出, 2 代表标准错误。

类型 描述符 默认值 系统文件
标准输入(standard input) 0 从键盘获取 /proc/self/fd/0
标准输出(standard output) 1 输出到屏幕 /proc/self/fd/1
错误输出(error output) 2 输出到屏幕 /proc/self/fd/2

在 Shell 中也可以改变默认的标准输入、标准输出或错误输出,来实现输入输出的重定向。比如将标准输出指向文件时,那么标准的输出就会保存到文件中。

# 重定向输出,用标准输出替换文件中的内容
[Linux]$ date > date.txt
[Linux]$ cat date.txt
Sat 22 Aug 2020 09:29:27 PM CST

# 重定向输出,将标准输出的内容追加到文件中
[Linux]$ cal >> date.txt
[Linux]$ cat date.txt
Sat 22 Aug 2020 09:29:27 PM CST
    August 2020
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31

提示

在重定向输出时(也包括下边的重定向标准错误),如果指定的文件不存在则会新建文件。一定要小心区分替换和追加的区别,不要丢失了重要的数据。

  • > 替换文件中的数据
  • >> 将输出追加到文件的末尾

Shell 中定义了两种不同的输出目标:标准输出和错误输出,标准输出用于正常输出,错误输出用于错误消息输出。重定向标准错误时,需要加入错误输出文件描述符(即数字 2 ),当重定向错误输出时,不会影响标准输入和标准输出。

[Linux]$ ls a.txt b
ls: cannot access 'b': No such file or directory
a.txt
[Linux]$ ls a.txt b 2> error
a.txt
[Linux]$ cat error
ls: cannot access 'b': No such file or directory

# 分开定义标准输出和错误输出
[Linux]$ ls a.txt b > out 2> error

# 将错误输出追加到文件中
[Linux]$ ls a.txt b 2>> error

如果想将标准输出和错误输出重定向到同一个位置,可以先重定向标准输出,然后在将错误输出指定到标准输出中。

[Linux]$ ls a.txt b > output 2>&1

除了重定向输出,还可以用 < 重定向输入。甚至同时指定重定向输入和输出。

[Linux]$ cat a.txt
a
c
d
b

# 重定向输入
[Linux]$ sort < a.txt
a
b
c
d

# 同时指定重定向输入和输出
[Linux]$ sort < a.txt > b.txt
[Linux]$ cat b.txt
a
b
c
d

提示

在 Linux 中,有一个特殊的设备文件,即 /dev/null ,它会丢弃一切写入其中的数据(但会反馈写入操作成功)。

在程序员行话中, 将 /dev/null 称为位桶(bit bucket)或黑洞(black hole),经常被用于丢弃不需要的输出流,或作为输入流的空文件。

如果不希望看到命令的错误信息,可以将错误输出重定向到 /dev/null 中。同时可以使用 cat /dev/null > a.txt 清空一个文件中的内容。

管道

在 Linux 设计原则里,每个命令都是一个小工具,每个工具只出色的完成一件事情。当靠一个工具无法解决问题时,能够使用一组命令来完成任务。Shell 允许创建一序列命令,将一个命令的标准输出发送到下一个命令的标准输入,两个命令之间需要用 | 符号连接,这时,一序列命令称为管道线(pipeline), | 符号称为管道(pipe)。

# 将文件 a 和文件 b 的内容发送到 less 命令中读取
[Linux]$ cat a.txt b.txt | less

上边的命令中,less 只处理 cat 的正确输出结果,如果文件 b.txt 不存在,则只会显示 a.txt 文件的内容。可以将 cat 命令的标准输出和错误输出一起发送给 less 命令,命令为 cat a.txt b.txt 2>&1 | less

提示

可以将管道想象成真实的水管,在污水处理中,污水(输出)从一端进入,经过一层过滤(命令)之后流向另一层再去过滤,直到污水被净化干净。

有时候,可能希望将程序的输出同时发送到两个地方。例如,希望将一个输出即保存在文件中,同时还发送到另一个程序。这时可以使用 tee 命令,tee 命令会从标准输入读取数据,将其内容输出到标准输出,同时保存成文件。命令语法为:

command 1 | tee file | command2

推荐阅读: tee 输出分流

危险

乍看起来,管道也有重定向的作用,它也改变了数据输入输出的方向,那么,管道和重定向之间到底有什么不同呢?

简单地说,重定向将命令与文件连接起来,用文件来接收命令的输出;而管道将命令与命令连接起来,用第二个命令来接收第一个命令的输出。来看一个特殊的例子:

# 注意这里是 root 用户
[Linux]# cd /usr/bin
[Linux]# ls > less

第一条命令将当前目录切换到了大多数程序所存放的目录,第二条命令是告诉 Shell 用 ls 命令的输出覆盖文件 less 中的内容。因为 /usr/bin 目录已经包含了 less(程序)文件,所以重定向会破坏西系统中的 less 程序。

这是使用重定向错误重写文件的一个教训,所以在使用时要谨慎。

在子 Shell 运行命令

当在 Shell 中执行命令时,首先 Shell 会判断这条命令是内部命令(Shell 中内置的命令)还是外部命令(单独的程序)。如果是内部命令就直接解释命令,如果是外部命令会创建一个 Shell 副本进程(即子 Shell)运行这个程序。当程序终止时,会重新将控制权交还给原 Shell (即父 Shell),并等待输入另一条命令。

将命令用小括号括起来执行,括号中的命令将会新开一个子 Shell 顺序执行,可以在括号中用 ; 组合多条命令,圆括号中的命令被称为一个编组。

[Linux]$ (cd ./file; ./a.py)

提示

当用 ssh 远程登陆主机执行,因为所有的命令都是 ssh 进程的子进程,所以当 ssh 断开连接时,所有的命令都会被杀死。如果想在断开连接时,命令依然在后台执行,可以将命令放入 () 中执行。

[Linux]$ (ping www.baidu.com > /dev/null &)
[Linux]$ ps -ef | grep ping
glenn     22202     1  0 21:22 pts/0    00:00:00 ping www.baidu.com
glenn    22204 21736  0 21:22 pts/0    00:00:00 grep ping

可以看到进程的父 ID 是 init 而不是当前终端的进程 ID,因而关闭 ssh 连接后无任何影响。