Linux 中的通配符与正则表达式

Linux 中的通配符与正则表达式

很多新手在操作 Linux 时会遇到一个非常令人困惑的现象。想要批量删除所有的 txt 后缀文件,输入 rm *.txt,系统执行得非常顺利。但是,当想要用 grep 去搜索包含特定内容的文件时,输入同样的 *.txt,命令窗口什么都搜不到。这两个地方用的明明都是星号,看起来一模一样,为什么待遇差别这么大?

1. Linux 中的通配符与正则表达式

要解决上面那个问题,必须先明白 Linux 系统里其实有两套完全不同的规则。第一套叫做通配符,第二套叫做正则表达式

简单来说,通配符是交给 Shell(也就是那个黑色的命令行窗口)去处理的,它的工作主要就是用来找文件,匹配文件名的。

正则表达式是交给具体的软件工具(比如 grepawk 或者编辑器)去处理的,它的工作是用来找文字,处理文件里面具体内容的。

分工不同,规则自然就不一样。

2. 通配符与正则表达式的使用场景

1. 使用通配符的场景

通配符的主战场就是 Shell 的文件名匹配

这就好比在点名。当在命令行里输入一个带有通配符的命令时,Shell 这个“管家”非常勤快。它会在命令真正执行之前,先把这些通配符全部翻译成具体的文件名。

举个例子,输入 ls *.txt。Shell 看到 *.txt 后,会先去当前目录下找找有没有结尾是 .txt 的文件。如果找到了 a.txtb.txt,Shell 就会把命令自动变成 ls a.txt b.txt,然后再交给 ls 命令去执行。ls 命令自己其实根本没看见那个星号,它看到的是已经被 Shell 替换好的完整文件名。

以下这些场景用的全是通配符:

文件管理命令

只要是涉及对文件本身进行操作的命令,基本都用通配符。
**ls**:列出文件。想看所有以 file 开头的文件,就用 ls file*
**rm**:删除文件。想删光所有 txt,就用 rm *.txt
cp 和 **mv**:复制或移动文件。把所有符合样子的文件搬到另一个地方。
**touch**:虽然一般用来新建文件,但如果配合通配符,也可以用来批量更新现有文件的时间戳。

Shell 脚本逻辑

for 循环:写脚本时经常需要轮询处理文件。

1
for tfile in *.txt; do ...

这里的 *.txt 会被 Shell 自动展开成一个文件列表,然后循环一个个处理。

文件搜索工具

**find**:这个命令有点特殊。它在 -name 参数里用的是通配符。但是,为了防止 Shell 这个“管家”太勤快,提前把星号给展开了,通常需要给参数加上引号。

1
find . -name 'music*'

加了引号,Shell 就不会乱动,而是把 music* 原封不动地传给 find 命令,让 find 自己去目录里慢慢比对文件名。

2. 使用正则表达式的场景

正则表达式完全是另一回事。它定义的是一套非常复杂的文本搜索、替换和过滤模式。它的眼睛盯着的是文件里面那一行行的字符数据,而不是文件外面的名字。

以下这些工具和场景,用的全是正则表达式:

文本搜索与处理工具

**grep**:这是最经典的正则工具,专门用来过滤包含特定内容的行。

1
grep '^K' tennis

这个命令的意思是,在 tennis 这个文件里,找出所有以 K 字符开头的行。这里的 ^ 就是正则符号,表示行首。

**sed**:流编辑器,用来做复杂的文本替换。

1
sed 's/Sun/Mon/'

把文本里的 Sun 换成 Mon,这里就在用正则匹配。

**awk**:强大的文本处理语言,提取数据时大量依赖正则。

编辑器

**vi / vim**:在编辑器里按 / 查找内容,或者用 :%s 替换内容时,输入的模式全都是正则表达式。

高级文件重命名

**rename**:在 Debian 等系统里,这个命令可以用正则表达式来改名。

1
rename 's/\.txt$/.TXT/' *

这比简单的 mv 强大得多,可以精细控制文件名里哪一部分要改,哪一部分保留。

Shell 历史记录操作

Bash History:这是个冷知识。可以用正则去搜索并替换历史命令里的打错的字。

1
!t:s/1/42/

特别注意:像 find 这种命令很狡猾,有些参数支持通配符,有些参数可能又支持正则。另外,在使用 grep 等正则工具时,强烈建议把写的模式用单引号包起来。为什么?因为如果不加引号,Shell 看到里面有星号或美元符号,可能会以为是通配符,自作主张给展开了,结果传给 grep 的东西就全乱套了。

3. 关键差异详解:为什么会搞混?

这里把最容易坑人的几个点详细拆开来讲。

星号 (*) 的本质区别

这是最大的误区。
通配符里,**\*** 是个独挡一面的大佬。它自己就代表“任意长度的任意字符”。写下 file*,它就能匹配 file1fileABCfile_long_name
正则表达式里,**\*** 只是个跟班,叫“量词”。它自己没有意义,必须跟在别人屁股后面,修饰前一个字符。它说的是:“前面那个家伙,可以出现零次,也可以出现无数次”。
比如正则里的 a*,匹配的是空字符串、aaaaaa
如果想在正则里表达“任意字符”,必须由“点号”(代表任意一个字符)和“星号”(代表重复)配合,写成 **.\***。

匹配逻辑的不同

通配符匹配通常是“全匹配”。Shell 拿着模式去套文件名,必须从头到尾对得上才算数。
正则表达式匹配通常是“包含匹配”。只要这一行里有任何一部分符合模式,grep 就认为匹配成功,把这行字显示出来。所以正则才需要 ^(行首)和 $(行尾)这种符号来把位置卡死。

符号的含义不同

通配符里的 ? 代表“必须要有一个字符”。
正则表达式里的 . 代表“除换行符外的任意一个字符”。
正则表达式里用 ^ 表示行首,**$** 表示行尾。通配符里压根没这东西,因为它默认就是匹配整个名字。

排除字符的写法不同

通配符里要排除某些字符,用 **[!...]**,里面是感叹号。
正则表达式里要排除某些字符,用 **[^...]**,里面是脱字符(小尖角)。


对比表

图片