Unix / Linux - 使用 SED 的正则表达式

在本章中,我们将详细讨论 Unix 中使用 SED 的正则表达式。

正则表达式是可用于描述多个字符序列的字符串。 正则表达式被几种不同的 Unix 命令使用,包括 ed, sed, awk, grep,以及在更有限的范围内,vi

这里的 SED 代表 stream editor。 这个面向流的编辑器专为执行脚本而创建。 因此,您输入的所有输入都会通过并进入 STDOUT,并且不会更改输入文件。


调用 sed

在我们开始之前,让我们确保我们有一个 /etc/passwd 文本文件的本地副本来使用 sed

如前所述,可以通过管道向其发送数据来调用 sed,如下所示 −

$ cat /etc/passwd | sed
Usage: sed [OPTION]... {script-other-script} [input-file]...

  -n, --quiet, --silent
                 suppress automatic printing of pattern space
  -e script, --expression = script
...............................

cat 命令通过管道将 /etc/passwd 的内容转储到 sed 的模式空间中。 模式空间是 sed 用于其操作的内部工作缓冲区。


sed 通用语法

以下是 sed 的一般语法 −

/pattern/action

这里,pattern是一个正则表达式,action是下表给出的命令之一。 如果 pattern 被省略,action 就会像我们上面看到的那样对每一行执行。

模式周围的斜杠字符 (/) 是必需的,因为它们用作定界符。

序号 范围 & 描述
1

p

打印行

2

d

删除行

3

s/pattern1/pattern2/

用 pattern2 替换第一次出现的 pattern1


用sed删除所有行

我们现在将了解如何使用 sed 删除所有行。 再次调用 sed; 但 sed 现在应该使用 编辑命令删除行,由单个字母 d 表示 −

$ cat /etc/passwd | sed 'd'
$

可以指示 sed 从文件中读取数据,而不是通过管道向其发送文件来调用 sed,如以下示例所示。

以下命令与前面的示例完全相同,只是没有 cat 命令 −

$ sed -e 'd' /etc/passwd
$

sed 地址

sed 也支持地址。 地址是文件中的特定位置或应应用特定编辑命令的范围。 当 sed 没有遇到地址时,它会对文件中的每一行执行操作。

以下命令将基本地址添加到您一直使用的 sed 命令 −

$ cat /etc/passwd | sed '1d' |more
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
$

注意在delete edit命令前加了数字1。 这指示 sed 在文件的第一行执行编辑命令。 在此示例中,sed 将删除 /etc/password 的第一行并打印文件的其余部分。


sed 地址范围

我们现在将了解如何使用 sed 地址范围。 那么如果你想从文件中删除多行怎么办? 您可以使用 sed 指定地址范围,如下所示 −

$ cat /etc/passwd | sed '1, 5d' |more
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
$

上述命令将应用于从 1 到 5 的所有行。这将删除前五行。

尝试以下地址范围 −

序号 范围 & 描述
1

'4,10d'

从第 4th 到第 10 行被删除

2

'10,4d'

只删除第 10th 行,因为 sed 不能反向工作

3

'4,+5d'

这匹配文件中的第 4 行,删除该行,继续删除接下来的五行,然后停止删除并打印其余行

4

'2,5!d'

这将删除除从第 2nd 到第 5 行之外的所有内容

5

'1~3d'

这会删除第一行,跨过接下来的三行,然后删除第四行。 Sed 继续应用此模式,直到文件结束。

6

'2~2d'

这告诉 sed 删除第二行,越过下一行,删除下一行,并重复直到到达文件末尾

7

'4,10p'

打印从第 4th 到第 10th

8

'4,d'

这会产生语法错误

9

',10d'

这也会产生语法错误

注意 − 在使用p 操作时,您应该使用-n 选项以避免重复行打印。 检查以下两个命令之间的区别 −

$ cat /etc/passwd | sed -n '1,3p'
Check the above command without -n as follows −
$ cat /etc/passwd | sed '1,3p'

替换命令

s 表示的替换命令将用您指定的任何其他字符串替换您指定的任何字符串。

要将一个字符串替换为另一个字符串,sed 需要了解第一个字符串的结束位置和替换字符串的开始位置。 为此,我们继续使用正斜杠 (/) 字符对两个字符串进行书尾处理。

以下命令用字符串 amrood 替换字符串 root 在一行中的第一次出现。

$ cat /etc/passwd | sed 's/root/amrood/'
amrood:x:0:0:root user:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
..........................

请务必注意,sed 仅替换一行中的第一次出现。 如果字符串根在一行中出现不止一次,则只有第一个匹配项将被替换。

为了让 sed 执行全局替换,在命令末尾添加字母g如下 −

$ cat /etc/passwd | sed 's/root/amrood/g'
amrood:x:0:0:amrood user:/amrood:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
...........................

替换标志

除了 g 标志之外,还有许多其他有用的标志可以传递,您可以一次指定多个。

序号 标志 & 描述
1

g

替换所有匹配项,而不仅仅是第一个匹配项

2

NUMBER

仅替换 NUMBERth 个匹配项

3

p

如果进行了替换,则打印模式空间

4

w FILENAME

如果进行了替换,则将结果写入 FILENAME

5

I 或 i

以不区分大小写的方式匹配

6

M 或 m

除了特殊正则表达式字符 ^ 和 $ 的正常行为外,此标志还导致 ^ 匹配换行符后的空字符串,$ 匹配换行符前的空字符串


使用替代字符串分隔符

假设您必须对包含正斜杠字符的字符串进行替换。 在这种情况下,您可以通过在 s 之后提供指定字符来指定不同的分隔符。

$ cat /etc/passwd | sed 's:/root:/amrood:g'
amrood:x:0:0:amrood user:/amrood:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh

在上面的示例中,我们使用 : 作为 分隔符 而不是斜杠 / 因为我们试图搜索 /root 而不是简单的 root。


替换为空白

使用空替换字符串从 /etc/passwd 文件中完全删除根字符串 −

$ cat /etc/passwd | sed 's/root//g'
:x:0:0::/:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh

地址替换

如果你只想在第10行用字符串quiet替换字符串sh,你可以指定如下 −

$ cat /etc/passwd | sed '10s/sh/quiet/g'
root:x:0:0:root user:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/quiet

类似地,要进行地址范围替换,您可以执行如下操作 −

$ cat /etc/passwd | sed '1,5s/sh/quiet/g'
root:x:0:0:root user:/root:/bin/quiet
daemon:x:1:1:daemon:/usr/sbin:/bin/quiet
bin:x:2:2:bin:/bin:/bin/quiet
sys:x:3:3:sys:/dev:/bin/quiet
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh

从输出中可以看出,前五行的字符串 sh 已更改为 quiet,但其余行保持不变。


匹配命令

您可以使用 p 选项和 -n 选项来打印所有匹配的行,如下所示 −

$ cat testing | sed -n '/root/p'
root:x:0:0:root user:/root:/bin/sh
[root@ip-72-167-112-17 amrood]# vi testing
root:x:0:0:root user:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh

使用正则表达式

在匹配模式时,您可以使用提供更大灵活性的正则表达式。

检查以下示例,它匹配所有以 daemon 开头的行,然后删除它们 −

$ cat testing | sed '/^daemon/d'
root:x:0:0:root user:/root:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh

以下是删除所有以 sh 结尾的行的示例 −

$ cat testing | sed '/sh$/d'
sync:x:4:65534:sync:/bin:/bin/sync

下表列出了四个在正则表达式中非常有用的特殊字符。

序号 字符 & 描述
1

^

匹配行的开头

2

$

匹配行尾

3

.

匹配任何单个字符

4

*

匹配前一个字符的零次或多次出现

5

[chars]

匹配 chars 中给出的任何一个字符,其中 chars 是一个字符序列。 您可以使用 - 字符来指示字符范围。


匹配字符

再看几个表达式来演示元字符的用法。 例如,下面的模式 −

下表显示了一些常用的字符集 −

序号 表达式 & 描述
1

/a.c/

匹配包含 a+c, a-c, abc, matcha3c 等字符串的行

2

/a*c/

匹配相同的字符串以及 aceyaccarctic 等字符串

3

/[tT]he/

匹配字符串 Thethe

4

/^$/

匹配空行

5

/^.*$/

匹配整行,不管它是什么

6

/ */

匹配一个或多个空格

7

/^$/

匹配空白

序号 字符集 & 描述
1

[a-z]

匹配单个小写字母

2

[A-Z]

匹配单个大写字母

3

[a-zA-Z]

匹配单个字母

4

[0-9]

匹配单个数字

5

[a-zA-Z0-9]

匹配单个字母或数字


字符类关键字

regexps 通常可以使用一些特殊关键字,尤其是使用 regexps 的 GNU 实用程序。 这些对于 sed 正则表达式非常有用,因为它们可以简化事情并提高可读性。

例如,字符 a 到 z 和字符 A 到 Z 构成了具有关键字 [[:alpha: ]]

使用 alphabet 字符类关键字,此命令仅打印 /etc/syslog.conf 文件中以字母开头的那些行 −

$ cat /etc/syslog.conf | sed -n '/^[[:alpha:]]/p'
authpriv.*                         /var/log/secure
mail.*                             -/var/log/maillog
cron.*                             /var/log/cron
uucp,news.crit                     /var/log/spooler
local7.*                           /var/log/boot.log

下表是 GNU sed 中可用字符类关键字的完整列表。

序号 字符类 & 描述
1

[[:alnum:]]

字母数字 [a-z A-Z 0-9]

2

[[:alpha:]]

字母 [a-z A-Z]

3

[[:blank:]]

空白字符(空格或制表符)

4

[[:cntrl:]]

控制字符

5

[[:digit:]]

数字[0-9]

6

[[:graph:]]

任何可见字符(不包括空格)

7

[[:lower:]]

小写字母 [a-z]

8

[[:print:]]

可打印字符(非控制字符)

9

[[:punct:]]

标点符号

10

[[:space:]]

空格

11

[[:upper:]]

大写字母 [A-Z]

12

[[:xdigit:]]

十六进制数字 [0-9 a-f A-F]


元字符引用

sed 元字符 & 表示匹配的模式的内容。 例如,假设您有一个包含电话号码的名为 phone.txt 的文件,如下所示 −

5555551212
5555551213
5555551214
6665551215
6665551216
7775551217

您希望将areacode区号(前三位数字)用括号括起来以便于阅读。 为此,您可以使用与号替换字符 −

$ sed -e 's/^[[:digit:]][[:digit:]][[:digit:]]/(&)/g' phone.txt
(555)5551212
(555)5551213
(555)5551214
(666)5551215

(666)5551216
(777)5551217

在模式部分,您匹配前 3 位数字,然后使用 & 将这 3 位数字替换为周围的括号


使用多个 sed 命令

您可以在单个 sed 命令中使用多个 sed 命令,如下所示 −

$ sed -e 'command1' -e 'command2' ... -e 'commandN' files

这里的 command1commandN 是前面讨论过的类型的 sed 命令。 这些命令应用于 files 给出的文件列表中的每一行。

使用相同的机制,我们可以将上面的电话号码示例写成如下 −

$ sed -e 's/^[[:digit:]]\{3\}/(&)/g'  \ 
   -e 's/)[[:digit:]]\{3\}/&-/g' phone.txt 
(555)555-1212 
(555)555-1213 
(555)555-1214 
(666)555-1215 
(666)555-1216 
(777)555-1217

注意 − 在上面的示例中,我们没有将字符类关键字 [[:digit:]] 重复三次,而是将其替换为 \{3\},这意味着前面的 正则表达式匹配三次。 我们还使用 \ 来换行,这必须在命令运行之前删除。


反向引用

反向引用 很有用,但更有用的是在正则表达式中定义特定区域的能力。 这些特殊区域可用作替换字符串中的参考。 通过定义正则表达式的特定部分,您可以使用特殊的引用字符来引用这些部分。

要进行反向引用,您必须先定义一个区域,然后再反向引用该区域。 要定义一个区域,您可以在每个感兴趣的区域周围插入反斜杠括号。用反斜杠包围的第一个区域由 \1 引用,第二个区域由 \2 引用,依此类推。

假设 phone.txt 有以下文本 −

(555)555-1212
(555)555-1213
(555)555-1214
(666)555-1215
(666)555-1216
(777)555-1217

试试下面的命令 −

$ cat phone.txt | sed 's/\(.*)\)\(.*-\)\(.*$\)/Area \ 
   code: \1 Second: \2 Third: \3/' 
Area code: (555) Second: 555- Third: 1212 
Area code: (555) Second: 555- Third: 1213 
Area code: (555) Second: 555- Third: 1214 
Area code: (666) Second: 555- Third: 1215 
Area code: (666) Second: 555- Third: 1216 
Area code: (777) Second: 555- Third: 1217

注意 − 在上面的示例中,括号内的每个正则表达式将被 \1\2 等反向引用。 我们在这里使用 \ 来换行。 这应该在运行命令之前删除。