【BASH】回顾与知识点梳理(八)

【BASH】回顾与知识点梳理 八

该系列目录 --> 【BASH】回顾与知识点梳理(目录)

八. 正则表达式(正规表示法)

正规表示法 (Regular Expression, RE, 或称为常规表示法)是透过一些特殊字符的排列,用以『搜寻/取代/删除』一列或多列文字字符串, 简单的说,正规表示法就是用在字符串的处理上面的一项『表示式』。正规表示法并不是一个工具程序,而是一个字符串处理的标准依据,如果您想要以正规表示法的方式处理字符串,就得要使用支持正规表示法的工具程序才行, 这类的工具程序很多,例如 vi, sed, awk 等等。

正规表示法对于系统管理员来说实在是很重要!因为系统会产生很多的讯息,这些讯息有的重要有的仅是告知, 此时,管理员可以透过正规表示法的功能来将重要讯息撷取出来,并产生便于查阅的报表来简化管理流程。此外, 很多的软件包也都支持正规表示法的分析,例如邮件服务器的过滤机制(过滤垃圾信件)就是很重要的一个例子。 所以,您最好要了解正规表示法的相关技能,在未来管理主机时,才能够更精简处理您的日常事务!

注:本章节使用者需要多加练习,因为目前很多的套件都是使用正规表示法来达成其『过滤、分析』的目的, 为了未来主机管理的便利性,使用者至少要能看的懂正规表示法的意义!

8.1 什么是正规表示法

约略了解了 Linux 的基本指令 (BASH) 并且熟悉了 vim 之后,相信你对于敲击键盘的打字与指令下达比较不陌生了吧? 接下来,底下要开始介绍一个很重要的观念,那就是所谓的『正规表示法(Regular Expression)』啰!

  • 什么是正规表示法

    任何一个有经验的系统管理员,都会告诉你:『正规表示法真是挺重要的!』 为什么很重要呢?因为日常生活就使用的到啊!举个例子来说, 在你日常使用 vim 作字处理或程序撰写时使用到的『搜寻/取代』等等的功能, 这些举动要作的漂亮,就得要配合正规表示法来处理啰!

    简单的说,正规表示法就是处理字符串的方法,他是以行为单位来进行字符串的处理行为, 正规表示法透过一些特殊符号的辅助,可以让使用者轻易的达到『搜寻/删除/取代』某特定字符串的处理程序!

    举个系统常见的例子好了,假设妳发现系统在开机的时候,老是会出现一个关于 mail 程序的错误, 而开机过程的相关程序都是在/lib/systemd/system/ 底下,也就是说,在该目录底下的某个文件内具有 mail 这个关键词,你想要将该文件捉出来进行查询修改的动作。此时你怎么找出来含有这个关键词的文件? 你当然可以一个文件一个文件的开启,然后去搜寻 mail 这个关键词,只是...该目录底下的文件可能不止 100 个说~ 如果了解正规表示法的相关技巧,那么只要一行指令就找出来啦:『grep 'mail' /lib/systemd/system/*』 那个 grep 就是支持正规表示法的工具程序之一!如何~很简单吧!

    谈到这里就得要进一步说明了,正规表示法基本上是一种『表示法』, 只要工具程序支持这种表示法,那么该工具程序就可以用来作为正规表示法的字符串处理之用。 例如 vi, grep, awk ,sed 等等工具,因为她们有支持正规表示法, 所以,这些工具就可以使用正规表示法的特殊字符来进行字符串的处理。但例如 cp, ls 等指令并未支持正规表示法, 所以就只能使用 bash 自己本身的通配符而已。

  • 正规表示法对于系统管理员的用途

    那么为何我需要学习正规表示法呢?对于一般使用者来说,由于使用到正规表示法的机会可能不怎么多, 因此感受不到他的魅力,不过,对于身为系统管理员的你来说,正规表示法则是一个『不可不学的好东西!』 怎么说呢?由于系统如果在繁忙的情况之下,每天产生的讯息信息会多到你无法想象的地步, 而我们也都知道,系统的『错误讯息登录文件』 的内容记载了系统产生的所有讯息,当然,这包含你的系统是否被『入侵』的记录数据。

  • 正规表示法的广泛用途

    正规表示法除了可以让系统管理员管理主机更为便利之外,事实上,由于正规表示法强大的字符串处理能力, 目前一堆软件都支持正规表示法呢!最常见的就是『邮件服务器』啦!

  • 正规表示法与 Shell 在 Linux 当中的角色定位

    说实在的,我们在学数学的时候,一个很重要、但是粉难的东西是一定要『背』的, 那就是九九表,背成功了之后,未来在数学应用的路途上,真是一帆风顺啊! 这个九九表我们在小学的时候几乎背了一整年才背下来,并不是这么好背的呢! 但他却是基础当中的基础!你现在一定受惠相当的多呢 ^_^!

  • 延伸的正规表示法

    唔!正规表示法还有分喔?没错喔!正规表示法的字符串表示方式依照不同的严谨度而分为: 基础正规表示法与延伸正规表示法。延伸型正规表示法除了简单的一组字符串处理之外,还可以作群组的字符串处理, 例如进行搜寻 VBird 或 netman 或 lman 的搜寻,注意,是『或(or)』而不是『和(and)』的处理, 此时就需要延伸正规表示法的帮助啦!藉由特殊的『 ( 』与『 | 』等字符的协助, 就能够达到这样的目的!不过,我们在这里主力仅是介绍最基础的基础正规表示法而已啦!好啦!清清脑门,咱们用功去啰!

有一点要向大家报告的,那就是:『正规表示法与通配符是完全不一样的东西!』 这很重要喔!因为『通配符 (wildcard) 代表的是 bash 操作接口的一个功能』,但正规表示法则是一种字符串处理的表示方式! 这两者要分的很清楚才行喔!所以,学习本章,请将前一章 bash 的通配符意义先忘掉吧!

8.2 基础正规表示法

既然正规表示法是处理字符串的一种表示方式,那么对字符排序有影响的语系数据就会对正规表示法的结果有影响! 此外,正规表示法也需要支持工具程序来辅助才行!所以,我们这里就先介绍一个最简单的字符串撷取功能的工具程序,那就是 grep 啰!前一章已经介绍过 grep 的相关选项与参数,本章着重在进阶的 grep 选项说明啰! 介绍完 grep 的功能之后,就进入正规表示法的特殊字符的处理能力了。

语系对正规表示法的影响

为什么语系的数据会影响到正规表示法的输出结果呢?我们知道文件其实记录的仅有 0 与 1,看到的字符文字与数字都是透过编码表转换来的。由于不同语系的编码数据并不相同,所以就会造成数据撷取结果的差异了。 举例来说,在英文大小写的编码顺序中,zh_TW.big5 及 C 这两种语系的输出结果分别如下:

bash 复制代码
LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z
LANG=zh_TW 时:0 1 2 3 4 ... a A b B c C d D ... z Z

上面的顺序是编码的顺序,我们可以很清楚的发现这两种语系明显就是不一样!如果你想要撷取大写字符而使用 A-Z 时, 会发现LANG=C 确实可以仅捉到大写字符 (因为是连续的) ,但是如果LANG=zh_TW.big5 时,就会发现到, 连同小写的 b-z 也会被撷取出来!因为就编码的顺序来看,big5 语系可以撷取到『 A b B c C ... z Z 』这一堆字符哩! 所以,使用正规表示法时,需要特别留意当时环境的语系为何, 否则可能会发现与别人不相同的撷取结果喔!

由于一般我们在练习正规表示法时,使用的是兼容于 POSIX 的标准,因此就使用『 C 』这个语系! 因此,底下的很多练习都是使用『 LANG=C 』这个语系数据来进行的喔! 另外,为了要避免这样编码所造成的英文与数字的撷取问题,因此有些特殊的符号我们得要了解一下的! 这些符号主要有底下这些意义:

描述
\[:alpha:] 匹配任意字母字符,不管大小写,a-z,A-Z
\[:alnum:] 匹配任意字母字符和数字,0-9,a-z,A-Z
\[:upper:] 匹配大写字母,A-Z
\[:lower:] 匹配小写字母,a-z
\[:digit:] 匹配数字,0-9
\[:blank:] 匹配空格或值表符
\[:cntrl:] 代表键盘上面的控制按键,亦即包括 CR, LF, Tab, Del... 等等
\[:graph:] 除了空格符 (空格键与 Tab 按键) 外的其他所有按键
\[:print:] 匹配任意可打印字符
\[:punct:] 匹配标点符号
\[:space:] 匹配任意空白字符,空格,制表,NL,FF,VT,CR
\[:xdigit:] 代表 16 进位的数字类型,因此包括: 0-9, A-F, a-f 的数字与字符

尤其上表中的:alnum:, :alpha:, :upper:, :lower:, :digit: 这几个一定要知道代表什么意思,因为他要比 a-z 或 A-Z 的用途要确定的很!好了,底下就让我们开始来玩玩进阶版的 grep 吧!

Dos和Windows采用回车+换行(CR+LF)表示下一行

而UNIX/Linux采用换行符(LF)表示下一行

苹果机(MAC OS系统)则采用回车符(CR)表示下一行

grep 的一些进阶选项

我们在前面的 grep 谈论过一些基础用法, 但其实 grep 还有不少的进阶用法喔

bash 复制代码
[dmtsai@study ~]$ grep [-A] [-B] [--color=auto] '搜寻字符串' filename
选项与参数:
-A :后面可加数字,为 after 的意思,除了列出该行外,以该行为锚点,后续的 n 行也列出来;
-B :后面可加数字,为 befer 的意思,除了列出该行外,以该行为锚点,前面的 n 行也列出来;
-n :显示匹配内容的行号
-i :忽略大小写
--color=auto 可将正确的那个撷取数据列出颜色
bash 复制代码
#范例一:用 dmesg 列出核心讯息,再以 grep 找出内含 qxl 那行
[dmtsai@study ~]$ dmesg | grep 'qxl' -
[ 0.522749] [drm] qxl: 16M of VRAM memory size
[ 0.522750] [drm] qxl: 63M of IO pages memory ready (VRAM domain)
[ 0.522750] [drm] qxl: 32M of Surface memory size
[ 0.650714] fbcon: qxldrmfb (fb0) is primary device
[ 0.668487] qxl 0000:00:02.0: fb0: qxldrmfb frame buffer device
# dmesg 可列出核心产生的讯息!包括硬件侦测的流程也会显示出来。
# 鸟哥使用的显卡是 QXL 这个虚拟卡,透过 grep 来 qxl 的相关信息,可发现如上信息。

#范例二:承上题,要将捉到的关键词显色,且加上行号来表示:
[dmtsai@study ~]$ dmesg | grep -n --color=auto 'qxl'
515:[ 0.522749] [drm] qxl: 16M of VRAM memory size
516:[ 0.522750] [drm] qxl: 63M of IO pages memory ready (VRAM domain)
517:[ 0.522750] [drm] qxl: 32M of Surface memory size
529:[ 0.650714] fbcon: qxldrmfb (fb0) is primary device
539:[ 0.668487] qxl 0000:00:02.0: fb0: qxldrmfb frame buffer device
# 除了 qxl 会有特殊颜色来表示之外,最前面还有行号喔!其实颜色显示已经是默认在 alias 当中了!

#范例三:承上题,在关键词所在行的前两行与后三行也一起捉出来显示
[dmtsai@study ~]$ dmesg | grep -n -A3 -B2 --color=auto 'qxl'
# 你会发现关键词之前与之后的数行也被显示出来!这样可以让你将关键词前后数据捉出来进行分析啦!

grep 是一个很常见也很常用的指令,他最重要的功能就是进行字符串数据的比对,然后将符合用户需求的字符串打印出来。 需要说明的是『grep 在数据中查寻一个字符串时,是以 "整行" 为单位来进行数据的撷取的!』也就是说,假如一个文件内有 10 行,其中有两行具有你所搜寻的字符串,则将那两行显示在屏幕上,其他的就丢弃了!

在 CentOS 7 当中,预设已经将 --color=auto 加入在 alias 当中了!用户就可以直接使用有关键词显色的 grep 啰!非常方便!

基础正规表示法练习

要了解正规表示法最简单的方法就是由实际练习去感受啦!所以在汇整正规表示法特殊符号前, 我们先以底下这个文件的内容来进行正规表示法的理解吧!先说明一下,底下的练习大前提是:

  • 语系已经使用『 export LANG=C; export LC_ALL=C 』的设定值;
  • grep 已经使用 alias 设定成为『 grep --color=auto 』

LANG=en_US.UTF-8,这里使用utf-8效果是一样的

练习文件

bash 复制代码
[root@node-135 yurq]# cat regular_express.txt
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.^M
GNU is free air not free beer.^M
Her hair is very beauty.^M
I can't finish the test.^M
Oh! The soup taste good.^M
motorcycle is cheap than car.
This window is clear.
the symbol '*' is represented as start.
Oh! My god!
The gd software is a library for drafting programs.^M
You are the best is mean you are the no. 1.
The world <Happy> is the same with "glad".
I like dog.
google is the best tools for search keyword.
goooooogle yes!
go! go! Let's go.
# I am VBird

这文件共有 22 行,最底下一行为空白行!现在开始我们一个案例一个案例的来介绍吧!

例题一、搜寻特定字符串

搜寻特定字符串很简单吧?假设我们要从刚刚的文件当中取得 the 这个特定字符串,最简单的方式就是这样:

bash 复制代码
[root@node-135 yurq]# grep -n 'the' regular_express.txt
8:I can't finish the test.^M
12:the symbol '*' is represented as start.
15:You are the best is mean you are the no. 1.
16:The world <Happy> is the same with "glad".
18:google is the best tools for search keyword.

那如果想要『反向选择』呢?也就是说,当该行没有 'the' 这个字符串时才显示在屏幕上,那就直接使用:

bash 复制代码
[root@node-135 yurq]# grep -vn 'the' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
...

如果你想要取得不论大小写的 the 这个字符串,则:

bash 复制代码
[root@node-135 yurq]# grep -in 'the' regular_express.txt
8:I can't finish the test.^M
9:Oh! The soup taste good.^M
12:the symbol '*' is represented as start.
...
例题二、利用中括号 \[\] 来搜寻集合字符

如果我想要搜寻 test 或 taste 这两个单字时,可以发现到,其实她们有共通的 't?st' 存在~这个时候,我可以这样来搜寻:

bash 复制代码
[root@node-135 yurq]# grep -n 't[ae]ste*' regular_express.txt
8:I can't finish the test.^M
9:Oh! The soup taste good.^M

了解了吧?其实 \[\] 里面不论有几个字符,他都仅代表某『一个』字符, 所以,上面的例子说明了,我需要的字符串是『tast』或『test』两个字符串而已! 而如果想要搜寻到有 oo 的字符时,则使用:

bash 复制代码
[dmtsai@study ~]$ grep -n 'oo' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
...

但是,如果我不想要 oo 前面有 g 的话呢?此时,可以利用在集合字符的反向选择 \^ 来达成:

bash 复制代码
[root@node-135 yurq]# grep -n '[^g]oo' regular_express.txt
2:apple is my favorite food.
3:Football game is not use feet only.
18:google is the best tools for search keyword.
19:goooooogle yes!

再来,假设我 oo 前面不想要有小写字符,所以,我可以这样写\^abcd...zoo , 但是这样似乎不怎么方便,由于小写字符的 ASCII 上编码的顺序是连续的, 因此,我们可以将之简化为底下这样:

bash 复制代码
[dmtsai@study ~]$ grep -n '[^a-z]oo' regular_express.txt
3:Football game is not use feet only.

也就是说,当我们在一组集合字符中,如果该字符组是连续的,例如大写英文/小写英文/数字等等,就可以使用a-z,A-Z,0-9等方式来书写,那么如果我们的要求字符串是数字与英文呢? 呵呵!就将他全部写在一起,变成:a-zA-Z0-9。例如,我们要取得有数字的那一行,就这样:

bash 复制代码
[root@node-135 yurq]# grep -n '[0-9]' regular_express.txt
5:However, this dress is about $ 3183 dollars.^M
15:You are the best is mean you are the no. 1.

但由于考虑到语系对于编码顺序的影响,因此除了连续编码使用减号『 - 』之外, 你也可以使用如下的方法来取得前面两个测试的结果:

bash 复制代码
[dmtsai@study ~]$ grep -n '[^[:lower:]]oo' regular_express.txt
# 那个 [:lower:] 代表的就是 a-z 的意思!
[dmtsai@study ~]$ grep -n '[[:digit:]]' regular_express.txt

啥?上头在写啥东西呢?不要害怕!分开来瞧一瞧。我们知道 :lower: 就是 a-z 的意思,那么 a-z 当然就是 \[:lower:] 啰!鸟哥第一次接触正规表示法的时候,看到两层中括号差点昏倒~完全看不懂!现在,请注意那个迭代的意义, 自然就能够比较清楚了解啰!

例题三、行首与行尾字符 ^ $

我们在例题一当中,可以查询到一行字符串里面有 the 的,那如果我想要让 the 只在行首列出呢?这个时候就得要使用制表符了!我们可以这样做:

bash 复制代码
[root@node-135 yurq]# grep -n '^the' regular_express.txt
12:the symbol '*' is represented as start.

如果我想要开头是小写字符的那一行就列出呢?可以这样:

bash 复制代码
[root@node-135 yurq]# grep -n '^[a-z]' regular_express.txt
2:apple is my favorite food.
4:this dress doesn't fit me.
10:motorcycle is cheap than car.
...

你可以发现我们可以捉到第一个字符都不是大写的!上面的指令也可以用如下的方式来取代的:

bash 复制代码
[root@node-135 yurq]# grep -n '^[[:lower:]]' regular_express.txt
2:apple is my favorite food.
4:this dress doesn't fit me.
...

不过如果中括号里再放一个^就是取反了

bash 复制代码
[root@node-135 yurq]# grep -n '^[^[:lower:]]' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
3:Football game is not use feet only.
...

那如果我不想要开头是英文字母,则可以是这样

bash 复制代码
[root@node-135 yurq]# grep -n '^[^a-zA-Z]' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
21:# I am VBird
# 指令也可以是: grep -n '^[^[:alpha:]]' regular_express.txt

注意到了吧?那个 ^ 符号,在字符集合符号(括号\[\])之内与之外是不同的!在 \[\] 内代表『反向选择』,在 \[\] 之外则代表定位在行首的意义!要分清楚喔! 反过来思考,那如果我想要找出来,行尾结束为小数点 (.) 的那一行,该如何处理:

bash 复制代码
[root@node-135 yurq]# grep -n '\.$' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
...

特别注意到,因为小数点具有其他意义(底下会介绍),所以必须要使用跳脱字符()来加以解除其特殊意义! 不过,你或许会觉得奇怪,但是第 5~9 行最后面也是 . 啊~怎么无法打印出来? 这里就牵涉到 Windows 平台的软件对于断行字符的判断问题了!我们使用 cat -A 将第五行拿出来看, 你会发现:

bash 复制代码
[dmtsai@study ~]$ cat -An regular_express.txt | head -n 10 | tail -n 6
 5 However, this dress is about $ 3183 dollars.^M$
 6 GNU is free air not free beer.^M$
 7 Her hair is very beauty.^M$
 8 I can't finish the test.^M$
 9 Oh! The soup taste good.^M$
 10 motorcycle is cheap than car.$

我们在前面谈到过断行字符在 Linux 与 Windows 上的差异, 在上面的表格中我们可以发现5~9 行为 Windows 的断行字符 (^M) ,而正常的 Linux 应该仅有第 10 行显示的那样 () 。所以啰,那个 . 自然就不是紧接在 之前喔!也就捉不到 5\~9 行了!这样可以了解 \^ 与 的意义吗?好了,先不要看底下的解答,自己想一想,那么如果我想要找出来,哪一行是『空白行』, 也就是说,该行并没有输入任何数据,该如何搜寻?

bash 复制代码
[root@node-135 yurq]# grep -n '^$' regular_express.txt
22:

因为只有行首跟行尾 (^$),所以,这样就可以找出空白行啦!再来,假设你已经知道在一个程序脚本 (shell script) 或者是配置文件当中,空白行与开头为 # 的那一行是批注,因此如果你要将资料列出给别人参考时, 可以将这些数据省略掉以节省保贵的纸张,那么你可以怎么作呢? 我们以/etc/rsyslog.conf 这个文件来作范例,你可以自行参考一下输出的结果:

bash 复制代码
[dmtsai@study ~]$ grep -v '^$' /etc/rsyslog.conf | grep -v '^#'
# 结果仅有 14 行,其中第一个『 -v '^$' 』代表『不要空白行』,
# 第二个『 -v '^#' 』代表『不要开头是 # 的那行』喔!
[root@node-135 yurq]# egrep -v '^#|^$' /etc/rsyslog.conf

是否节省很多版面啊?另外,你可能也会问,那为何不要出现 # 的符号的那行就直接舍弃呢?没办法!因为某些批注是与设定写在同一行的后面, 如果你只是抓 # 就予以去除,那就会将某些设定也同时移除了!那错误就大了~

例题四、任意一个字符 . 与重复字符 *

在前面介绍中,我们知道通配符 * 可以用来代表任意(0 或多个)字符, 但是正规表示法并不是通配符,两者之间是不相同的! 至于正规表示法当中的『 . 』则代表『绝对有一个任意字符』的意思!这两个符号在正规表示法的意义如下:

  • .(小数点):代表『一定有一个任意字符』的意思;
  • * (星星号):代表『重复前一个字符, 0 到无穷多次』的意思,为组合形态

这样讲不好懂,我们直接做个练习吧!假设我需要找出 g??d 的字符串,亦即共有四个字符, 起头是 g 而结束是 d ,我可以这样做:

bash 复制代码
[root@node-135 yurq]# grep -n 'g..d' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
9:Oh! The soup taste good.^M
16:The world <Happy> is the same with "glad".

因为强调 g 与 d 之间一定要存在两个字符,因此,第 13 行的 god 与第 14 行的 gd 就不会被列出来啦!再来,如果我想要列出有 oo, ooo, oooo 等等的数据, 也就是说,至少要有两个(含) o 以上,该如何是好?是 o* 还是 oo* 还是 ooo* 呢? 虽然你可以试看看结果, 不过结果太占版面了@_@ ,所以,我这里就直接说明。

因为 * 代表的是『重复 0 个或多个前面的 RE 字符』的意义, 因此,『o*』代表的是:『拥有空字符或一个 o 以上的字符』, 特别注意,因为允许空字符(就是有没有字符都可以的意思),因此,『 grep -n 'o*' regular_express.txt 』将会把所有的数据都打印出来屏幕上!

那如果是『oo*』呢?则第一个 o 肯定必须要存在,第二个 o 则是可有可无的多个 o , 所以,凡是含有 o, oo, ooo, oooo 等等,都可以被列出来~

同理,当我们需要『至少两个 o 以上的字符串』时,就需要 ooo* ,亦即是:

bash 复制代码
[dmtsai@study ~]$ grep -n 'ooo*' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
9:Oh! The soup taste good.
18:google is the best tools for search keyword.
19:goooooogle yes!

这样理解 * 的意义了吗?好了,现在出个练习,如果我想要字符串开头与结尾都是 g,但是两个 g 之间仅能存在至少一个 o ,亦即是 gog, goog, gooog... 等等,那该如何?

bash 复制代码
[dmtsai@study ~]$ grep -n 'goo*g' regular_express.txt
18:google is the best tools for search keyword.
19:goooooogle yes!

如此了解了吗?再来一题,如果我想要找出 g 开头与 g 结尾的字符串,当中的字符可有可无,那该如何是好?是『g*g』吗?

bash 复制代码
[root@node-135 yurq]# grep -n 'g.*g' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
14:The gd software is a library for drafting programs.^M
18:google is the best tools for search keyword.
19:goooooogle yes!
20:go! go! Let's go.

.* 就代表零个或多个任意字符』的意思啦!

因为是代表 g 开头与 g 结尾,中间任意字符均可接受,所以,第 1, 14, 20 行是可接受的喔!这个 .* 的 RE 表示任意字符是很常见的,希望大家能够理解并且熟悉! 再出一题,如果我想要找出『任意数字』的行列呢?因为仅有数字,所以就成为:

bash 复制代码
[dmtsai@study ~]$ grep -n '[0-9][0-9]*' regular_express.txt
5:However, this dress is about $ 3183 dollars.
15:You are the best is mean you are the no. 1.

虽然使用 grep -n '[0-9]' regular_express.txt 也可以得到相同的结果, 但鸟哥希望大家能够理解上面指令当中 RE 表示法的意义才好!

例题五、限定连续 RE 字符范围 {}

在上个例题当中,我们可以利用 . 与 RE 字符及 * 来设定 0 个到无限多个重复字符, 那如果我想要限制一个范围区间内的重复字符数呢?举例来说,我想要找出两个到五个 o 的连续字符串,该如何作?这时候就得要使用到限定范围的字符 {} 了。 但因为 { 与 } 的符号在 shell 是有特殊意义的,因此, 我们必须要使用跳脱字符 \ 来让他失去特殊意义才行。至于 {} 的语法是这样的,假设我要找到两个 o 的字符串,可以是:

bash 复制代码
[root@node-135 yurq]# grep -n 'o\{2\}' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
...

这样看似乎与 ooo* 的字符没有什么差异啊?因为第 19 行有多个 o 依旧也出现了! 好,那么换个搜寻的字符串,假设我们要找出 g 后面接 2 到 5 个 o ,然后再接一个 g 的字符串,他会是这样:

bash 复制代码
[root@node-135 yurq]# grep -n 'go\{2,5\}g' regular_express.txt
18:google is the best tools for search keyword.

嗯!很好!第 19 行终于没有被取用了(因为 19 行有 6 个 o 啊!)。 那么,如果我想要的是 2 个o 以上的 goooo...g 呢?除了可以是 gooo*g ,也可以是:

bash 复制代码
[root@node-135 yurq]# grep -n 'go\{2,\}g' regular_express.txt
18:google is the best tools for search keyword.
19:goooooogle yes!

8.3 基础正规表示法字符汇整 (characters)

RE 字符 意义与范例
^word 意义:待搜寻的字符串(word)在行首!范例:搜寻行首为 # 开始的那一行,并列出行号。grep -n '^#' regular_express.txt
word$ 意义:待搜寻的字符串(word)在行尾!范例:将行尾为 ! 的那一行打印出来,并列出行号。grep -n '!$' regular_express.txt
. 意义:代表『一定有一个任意字符』的字符!范例:搜寻的字符串可以是 (eve) (eae) (eee) (e e), 但不能仅有 (ee) !亦即 e 与 e 中间『一定』仅有一个字符,而空格符也是字符!grep -n 'e.e' regular_express.txt
\ 意义:跳脱字符,将特殊符号的特殊意义去除!范例:搜寻含有单引号 ' 的那一行!grep -n ' regular_express.txt
* 意义:重复零个到无穷多个的前一个 RE 字符。范例:找出含有 (es) (ess) (esss) 等等的字符串,注意,因为 * 可以是 0 个,所以 es 也是符合带搜寻字符串。另外,因为 * 为重复『前一个 RE 字符』的符号, 因此,在 * 之前必须要紧接着一个 RE 字符喔!例如任意字符则为 『.*』 !grep -n 'ess*' regular_express.txt
[list] 意义:字符集合的 RE 字符,里面列出想要撷取的字符!范例搜寻含有 (gl) 或 (gd) 的那一行,需要特别留意的是,在 \[\] 当中『谨代表一个待搜寻的字符』, 例如『 aafly 』代表搜寻的字符串可以是 aay, afy, aly 即 afl 代表 a 或f 或 l 的意思!grep -n 'gld' regular_express.txt
`n1-n2` 意义:字符集合的 RE 字符,里面列出想要撷取的字符范围!范例:搜寻含有任意数字的那一行!需特别留意,在字符集合 \[\] 中的减号 - 是有特殊意义的,他代表两个字符之间的所有连续字符!但这个连续与否与 ASCII 编码有关,因此,你的编码需要设定正确(在 bash 当中,需要确定 LANG 与 LANGUAGE 的变量是否正确!) 例如所有大写字符则为 A-Z。grep -n 'A-Z' regular_express.txt
[^list] 意义:字符集合的 RE 字符,里面列出不要的字符串或范围!范例:搜寻的字符串可以是 (oog) (ood) 但不能是 (oot) ,那个 ^ 在 \[\] 内时,代表的意义是『反向选择』的意思。 例如,我不要大写字符,则为 \^A-Z。但是,需要特别注意的是,如果以 grep -n \^A-Z regular_express.txt 来搜寻,却发现该文件内的所有行都被列出,为什么?因为这个 \^A-Z 是『非大写字符』的意思, 因为每一行均有非大写字符,例如第一行的 "Open Source" 就有 p,e,n,o... 等等的小写字。grep -n 'oo\^t' regular_express.txt
\{n,m\} 意义:连续 n 到 m 个的『前一个 RE 字符』意义:若为 {n} 则是连续 n 个的前一个 RE 字符,意义:若是 {n,} 则是连续 n 个以上的前一个 RE 字符! 范例:在 g 与 g 之间有 2 个到3 个的 o 存在的字符串,亦即 (goog)(gooog)grep -n 'go{2,3}g' regular_express.txt

再次强调:『正规表示法的特殊字符』与一般在指令列输入指令的『通配符』并不相同 , 例如,在通配符当中的 * 代表的是『 0 ~ 无限多个字符』的意思,但是在正规表示法当中, * 则是『重复 0 到无穷多个的前一个 RE 字符』的意思~使用的意义并不相同,不要搞混了!

举例来说,不支持正规表示法的 ls 这个工具中,若我们使用 『ls -l * 』 代表的是任意档名的文件,而 『ls -l a* 』代表的是以 a 为开头的任何档名的文件, 但在正规表示法中,我们要找到含有以 a 为开头的文件,则必须要这样:(需搭配支持正规表示法的工具)

bash 复制代码
ls | grep -n '^a.*'

例题:

以 ls -l 配合 grep 找出 /etc/ 底下文件类型为链接文件属性的文件名

答:

由于 ls -l 列出连结档时标头会是『 lrwxrwxrwx 』,因此使用如下的指令即可找出结果:

bash 复制代码
ls -l /etc | grep '^l'

若仅想要列出几个文件,再以『 |wc -l 』 来累加处理即可。

8.4 sed 工具(重点)

在了解了一些正规表示法的基础应用之后,再来呢?呵呵~两个东西可以玩一玩的,那就是 sed 跟底下会介绍的 awk 了! 这两个家伙可是相当的有用的啊!举例来说,鸟哥写的 logfile.sh 分析登录文件的小程序,绝大部分分析关键词的取用、统计等等,就是用这两个宝贝蛋来帮我完成的!那么你说,要不要玩一玩啊?^_^

我们先来谈一谈 sed 好了, sed 本身也是一个管线命令,可以分析 standard input 的啦! 而且 sed 还可以将数据进行取代、删除、新增、撷取特定行等等的功能呢!很不错吧~ 我们先来了解一下 sed 的用法,再来聊他的用途好了!

bash 复制代码
[dmtsai@study ~]$ sed [-nefri] [动作]
选项与参数:
-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到屏幕上。但如果加上 -n 参数后,
		则只有经过 sed 特殊处理的那一行(或者动作)才会被列出来。
-e :可以在一行里执行多条命令(原文:直接在指令列模式上进行 sed 的动作编辑);
-f :直接将 sed 的动作写在一个文件内, -f filename 则可以执行 filename 内的 sed 动作;
-r :sed 的动作支持的是延伸型正规表示法的语法。(预设是基础正规表示法语法)
-i :直接修改读取的文件内容,而不是由屏幕输出。

动作说明: [n1[,n2]] function
n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』

function 有底下这些咚咚:
a :新增(append), a 的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)~
c :取代(replace), c 的后面可以接字符串,这些字符串可以取代 n1,n2 之间的行!
d :删除(delete),因为是删除啊,所以 d 后面通常不接任何咚咚;
i :插入(insert), i 的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行);
p :打印(print),亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运作~
s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!
 例如 1,20s/old/new/g 就是啦!

以行为单位的新增/删除功能

sed 光是用看的是看不懂的啦!所以又要来练习了!先来玩玩删除与新增的功能吧!

bash 复制代码
#范例一:将 /etc/passwd 的内容列出并且打印行号,同时,请将第 2~5 行删除!
[dmtsai@study ~]$ nl /etc/passwd | sed '2,5d'
 1 root:x:0:0:root:/root:/bin/bash
 6 sync:x:5:0:sync:/sbin:/bin/sync
 7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
.....(后面省略).....

NAME

nl - number lines of files

SYNOPSIS

nl OPTION... FILE...

看到了吧?sed 的动作为 '2,5d' ,那个 d 就是删除!因为 2-5 行给他删除了,所以显示的数据就没有 2-5 行啰~ 另外,注意一下,原本应该是要下达 sed -e 才对,没有 -e 也行啦!同时也要注意的是, sed 后面接的动作,请务必以 '' 两个单引号括住喔!

如果题型变化一下,举例来说,如果只要删除第 2 行,可以使用『nl /etc/passwd | sed '2d'』来达成,至于若是要删除第 3 到最后一行,则是『 nl /etc/passwd | sed '3,$d' 』的啦,那个钱字号『 $ 』代表最后一行!

bash 复制代码
#范例二:承上题,在第二行后(亦即是加在第三行)加上『drink tea?』字样!
[dmtsai@study ~]$ nl /etc/passwd | sed '2a drink tea'
 1 root:x:0:0:root:/root:/bin/bash
 2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
.....(后面省略).....

嘿嘿!在 a 后面加上的字符串就已将出现在第二行后面啰!那如果是要在第二行前呢?『 nl /etc/passwd | sed '2i drink tea' 』就对啦!就是将『 a 』变成『 i 』即可。 增加一行很简单,那如果是要增加两行以上呢?

bash 复制代码
#范例三:在第二行后面加入两行字,例如『Drink tea or .....』与『drink beer?』
[dmtsai@study ~]$ nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?'
 1 root:x:0:0:root:/root:/bin/bash
 2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
.....(后面省略).....

这个范例的重点是『我们可以新增不只一行喔!可以新增好几行』但是每一行之间都必须要以反斜杠『 \ 』来进行新行的增加喔!所以,上面的例子中,我们可以发现在第一行的最后面就有 \ 存在啦!在多行新增的情况下, \ 是一定要的喔!

以行为单位的取代与显示功能

刚刚是介绍如何新增与删除,那么如果要整行取代呢?看看底下的范例吧:

bash 复制代码
#范例四:我想将第 2-5 行的内容取代成为『No 2-5 number』呢?
[dmtsai@study ~]$ nl /etc/passwd | sed '2,5c No 2-5 number'
 1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
 6 sync:x:5:0:sync:/sbin:/bin/sync
.....(后面省略).....

透过这个方法我们就能够将数据整行取代了!非常容易吧!sed 还有更好用的东东!我们以前想要列出第 11~20 行, 得要透过『head -n 20 | tail -n 10』之类的方法来处理,很麻烦啦~ sed 则可以简单的直接取出你想要的那几行!是透过行号来捉的喔!看看底下的范例先:

bash 复制代码
#范例五:仅列出 /etc/passwd 文件内的第 5-7 行
[dmtsai@study ~]$ nl /etc/passwd | sed -n '5,7p'
 5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
 6 sync:x:5:0:sync:/sbin:/bin/sync
 7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

上述的指令中有个重要的选项『 -n 』,按照说明文件,这个 -n 代表的是『安静模式』! 那么为什么要使用安静模式呢?你可以自行下达 sed '5,7p' 就知道了 (5-7 行会重复输出)! 有没有加上 -n 的参数时,输出的数据可是差很多的喔!你可以透过这个 sed 的以行为单位的显示功能, 就能够将某一个文件内的某些行号捉出来查阅!很棒的功能!不是吗?

部分数据的搜寻并取代的功能

除了整行的处理模式之外, sed 还可以用行为单位进行部分数据的搜寻并取代的功能喔! 基本上sed 的搜寻与取代的与 vi 相当的类似!他有点像这样:

bash 复制代码
sed 's/要被取代的字符串/新的字符串/g'

上表中特殊字体的部分(s///g)为关键词,请记下来!至于三个斜线分成两栏就是新旧字符串的替换啦! 我们使用底下这个取得 IP 数据的范例,一段一段的来处理给您瞧瞧,让你了解一下什么是咱们所谓的搜寻并取代吧!

  • 步骤一:先观察原始讯息,利用 /sbin/ifconfig 查询 IP 为何?
bash 复制代码
[dmtsai@study ~]$ /sbin/ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
 inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
 inet6 fe80::5054:ff:fedf:e174 prefixlen 64 scopeid 0x20<link>
 ether 52:54:00:df:e1:74 txqueuelen 1000 (Ethernet)
.....(以下省略).....
# 因为我们还没有讲到 IP ,这里你先有个概念即可啊!我们的重点在第二行,
# 也就是 192.168.1.100 那一行而已!先利用关键词捉出那一行!
  • 步骤二:利用关键词配合 grep 撷取出关键的一行数据
bash 复制代码
[dmtsai@study ~]$ /sbin/ifconfig eth0 | grep 'inet '
 inet 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# 当场仅剩下一行!要注意, CentOS 7 与 CentOS 6 以前的 ifconfig 指令输出结果不太相同,
# 鸟哥这个范例主要是针对 CentOS 7 以后的喔!接下来,我们要将开始到 addr: 通通删除,
# 就是像底下这样:
# 192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# 上面的删除关键在于『 ^.*inet 』啦!正规表示法出现! ^_^
  • 步骤三:将 IP 前面的部分予以删除
bash 复制代码
[dmtsai@study ~]$ /sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'
192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# 仔细与上个步骤比较一下,前面的部分不见了!接下来则是删除后续的部分,亦即:
192.168.1.100 netmask 255.255.255.0 broadcast 192.168.1.255
# 此时所需的正规表示法为:『 ' *netmask.*$ 』就是啦!
  • 步骤四:将 IP 后面的部分予以删除
bash 复制代码
[dmtsai@study ~]$ /sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' \
> | sed 's/ *netmask.*$//g'
192.168.1.100

透过这个范例的练习也建议您依据此一步骤来研究你的指令!就是先观察,然后再一层一层的试做,如果有做不对的地方,就先予以修改,改完之后测试,成功后再往下继续测试。以鸟哥上面的介绍中,那一大串指令就做了四个步骤!对吧!^_^

让我们再来继续研究 sed 与正规表示法的配合练习!假设我只要 MAN 存在的那几行数据, 但是含有 # 在内的批注我不想要,而且空白行我也不要!此时该如何处理呢?可以透过这几个步骤来实作看看:

  • 步骤一:先使用 grep 将关键词 MAN 所在行取出来
bash 复制代码
[dmtsai@study ~]$ cat /etc/man_db.conf | grep 'MAN'
# MANDATORY_MANPATH manpath_element
# MANPATH_MAP path_element manpath_element
# MANDB_MAP global_manpath [relative_catpath]
# every automatically generated MANPATH includes these fields
....(后面省略)....
  • 步骤二:删除掉批注之后的数据!
bash 复制代码
[dmtsai@study ~]$ cat /etc/man_db.conf | grep 'MAN'| sed 's/#.*$//g'

MANDATORY_MANPATH /usr/man
....(后面省略)....
# 从上面可以看出来,原本批注的数据都变成空白行啦!所以,接下来要删除掉空白行
  • 删除空白行
bash 复制代码
[dmtsai@study ~]$ cat /etc/man_db.conf | grep 'MAN'| sed 's/#.*$//g' | sed '/^$/d'
MANDATORY_MANPATH /usr/man
MANDATORY_MANPATH /usr/share/man
MANDATORY_MANPATH /usr/local/share/man
....(后面省略)....
[root@node-135 yurq]# egrep -v '^#|^$' /etc/man_db.conf |grep '^MAN'

直接修改文件内容(危险动作)

你以为 sed 只有这样的能耐吗?那可不! sed 甚至可以直接修改文件的内容呢!而不必使用管线命令或数据流重导向! 不过,由于这个动作会直接修改到原始的文件,所以请你千万不要随便拿系统配置文件来测试喔! 我们还是使用你下载的 regular_express.txt 文件来测试看看吧!

bash 复制代码
#范例六:利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !
[dmtsai@study ~]$ sed -i 's/\.$/\!/g' regular_express.txt
# 上头的 -i 选项可以让你的 sed 直接去修改后面接的文件内容而不是由屏幕输出喔!
# 这个范例是用在取代!请您自行 cat 该文件去查阅结果啰!

#范例七:利用 sed 直接在 regular_express.txt 最后一行加入『# This is a test』
[dmtsai@study ~]$ sed -i '$a # This is a test' regular_express.txt
# 由于 $ 代表的是最后一行,而 a 的动作是新增,因此该文件最后新增啰!

sed 的『 -i 』选项可以直接修改文件内容,这功能非常有帮助!举例来说,如果你有一个 100 万行的文件,你要在第 100 行加某些文字,此时使用 vim 可能会疯掉!因为文件太大了!那怎办?就利用 sed 啊!透过 sed 直接修改/取代的功能,你甚至不需要使用 vim 去修订!很棒吧!总之,这个 sed 不错用啦!而且很多的 shell script 都会使用到这个指令的功能~ sed 可以帮助系统管理员管理好日常的工作喔!要仔细的学习呢!

同时执行多条sed命令

sed 命令在默认情况下使用的是 -e 选项。当只有一个编辑命令时,-e 选项可以省略;但是当要在一条 sed 语句中执行多个编辑命令时,就需要使用 -e 选项了

bash 复制代码
[root@node-135 yurq]# cat regular_express.txt
"Open Source" is a good mechanism to develop programs!
apple is my favorite food!
Football game is not use feet only!
this dress doesn't fit me!
However, this dress is about $ 3183 dollars.^M
GNU is free air not free beer.^M
Her hair is very beauty.^M
...
[root@node-135 yurq]# sed -e 's/\!$/\./g;s/\^M//g' regular_express.txt
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.
GNU is free air not free beer.
Her hair is very beauty.
...

sed脚本文件

sed 的『 -f 』选项在处理输入时,将 file 中指定的编辑命令添加到已有的命令中:

前面提到,在需要同时执行多条编辑命令时,可以使用 -e 选项。但是当所需要执行的编辑命令数量很多时,每次使用 sed 时一行一行地敲显然不是很方便,这时可以将所用到的 sed 编辑命令写入一个文件,然后使用 sed -f 选项来指定读取该文件:

bash 复制代码
[root@node-135 yurq]# cat file.sed
s/bin/BIN/g
s/root/ROOT/g
s/yurq/YURQ/g
[root@node-135 yurq]# sed -f file.sed /etc/passwd
ROOT:x:0:0:ROOT:/ROOT:/BIN/bash
BIN:x:1:1:BIN:/BIN:/sBIN/nologin
...

该系列目录 --> 【BASH】回顾与知识点梳理(目录)

相关推荐
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区13 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大13 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa14 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio