Unix 简明教程

Unix / Linux - Regular Expressions with SED

在本篇教程中,我们将详细讨论 Unix 中的 SED 正则表达式。

正则表达式是一个可以用来描述多个字符序列的字符串。包括 edsedawkgrep 等许多不同的 Unix 命令都使用了正则表达式,在一定程度上甚至包括 vi

此处 SED 代表 *s*tream *ed*itor。此流定向编辑器完全用于执行脚本。因此,你输入其中的所有内容都将经过处理并传递到 STDOUT,并且它不会改变输入文件。

Invoking 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 用于执行操作的内部工作缓冲区。

The sed General Syntax

以下是 sed 的通用语法——

/pattern/action

此处, pattern 是正则表达式,而 action 是下表中给出的一个命令。如果 pattern 被忽略,则对每一行执行 action ,如我们在上面看到的那样。

把模式包围起来的斜杠 (/)是必需的,因为它们用作分隔符。

Sr.No.

Range & Description

1

p Prints the line

2

d Deletes the line

3

s/pattern1/pattern2/ 用 pattern2 替换 pattern1 的首次出现

Deleting All Lines with sed

现在我们将了解如何使用 sed 删除所有行。再次调用 sed;但是,现在 sed 应该使用 editing command delete line ,该字符用字母 d - 表示

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

不需要通过管道向 sed 发送文件即可调用 sed,可以指示 sed 从文件读取数据,如下面的示例所示。

以下命令与前一个示例中的命令完全相同,不需要 cat 命令:

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

The sed Addresses

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 对文件的第 1 行执行编辑命令。在此示例中,sed 将删除 /etc/password 的第一行并打印文件的其余部分。

The sed Address Ranges

现在我们将了解如何使用 the sed address ranges 。那么,如果您想从文件中移除多行,该怎么办?您可以用 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 开始的所有行。这会删除前五行。

尝试以下地址范围:

Sr.No.

Range & Description

1

'4,10d' 从第 4 行到第 10 行的各行会被删除

2

'10,4d' 只有第 10 行会被删除,因为 sed 不支持反向执行

3

'4,+5d' 该命令匹配文件中的第 4 行,删除该行,继续删除后续 5 行,然后停止删除并打印其余部分

4

'2,5!d' 该命令会删除除第 2 行到第 5 行以外的所有内容

5

'1~3d' 该命令会删除第 1 行,跳过后续三行,然后删除第 4 行。Sed 继续应用此模式,直到文件末尾。

6

'2~2d' 该命令告诉 sed 删除第 2 行,跳过后续一行,删除后续一行,重复此过程直到文件结尾

7

'4,10p' 从第 4 行到第 10 行的各行会被打印

8

'4,d' 该命令会生成语法错误

9

',10d' 该命令也会生成语法错误

Note − 在使用 p 操作时,您应该使用 -n 选项来避免重复打印行。查看以下两个命令之间的差异 −

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

The Substitution Command

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 仅替换一行中的首次出现。如果字符串 root 在一行中出现多次,则仅替换第一个匹配项。

为了让 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
...........................

Substitution Flags

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

Sr.No.

Flag & Description

1

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

2

NUMBER Replaces only NUMBERth match

3

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

4

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

5

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

6

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

Using an Alternative String Separator

假设您必须对包含正斜杠字符的字符串执行替换。在这种情况下,您可以通过在 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

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

Replacing with Empty Space

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

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

Address Substitution

如果您只想在第 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 ,但其余行保持不变。

The Matching Command

您将使用 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

Using Regular Expression

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

检查以下示例,它匹配以 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

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

Sr.No.

Character & Description

1

^ 匹配行首

2

$ 匹配行尾

3

. Matches any single character

4

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

5

[chars] 匹配 chars 中给定的任何一个字符,其中 chars 是一个字符序列。可以使用 - 字符来表示字符范围。

Matching Characters

再看一些表达式,以演示 metacharacters 的用法。例如,以下模式 −

Sr.No.

Expression & Description

1

/a.c/ 匹配包含诸如 a+ca-cabcmatcha3c 等字符串的行

2

/a*c/ 匹配相同的字符串以及 aceyaccarctic 等字符串

3

/[tT]he/ 匹配字符串 Thethe

4

/^$/ Matches blank lines

5

/^. $/* 匹配整行无论内容是什么

6

/ */ 匹配一个或多个空格

7

/^$/ Matches blank lines

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

Sr.No.

Set & Description

1

[a-z] 匹配单个小写字母

2

[A-Z] 匹配单个大写字母

3

[a-zA-Z] Matches a single letter

4

[0-9] Matches a single number

5

[a-zA-Z0-9] 匹配单个字母或数字

Character Class Keywords

某些特殊关键字通常可用于 regexps ,尤其是使用 regexps 的 GNU 实用程序。这些对于 sed 正则表达式非常有用,因为它们简化了操作并增强了可读性。

例如,字符 a through z 和字符 A through Z 构成包含关键字 [id=":alpha:"] 的此类字符。

使用字母字符类关键字,此命令只打印 /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 中可用字符类关键字的完整列表。

Sr.No.

Character Class & Description

1

[id=":alnum:"] Alphanumeric [a-z A-Z 0-9]

2

[id=":alpha:"] Alphabetic [a-z A-Z]

3

[id=":blank:"] 空白字符(空格或制表符)

4

[id=":cntrl:"] Control characters

5

[id=":digit:"] Numbers [0-9]

6

[id=":graph:"] 任意可见字符(不包含空白)

7

[id=":lower:"] Lowercase letters [a-z]

8

[id=":print:"] Printable characters (non-control characters)

9

[id=":punct:"] Punctuation characters

10

[id=":space:"] Whitespace

11

[id=":upper:"] Uppercase letters [A-Z]

12

[id=":xdigit:"] 十六进制数字 [0-9 a-f A-F]

Aampersand Referencing

sed metacharacter & 表示匹配的模式的内容。例如,假设你有一个名为 phone.txt 的文件,其中包含许多电话号码,如下所示 −

5555551212
5555551213
5555551214
6665551215
6665551216
7775551217

你想将 area code (前三个数字)括在括号中以方便阅读。为此,你可以使用替换字符“&” −

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

(666)5551216
(777)5551217

在模式部分,你匹配前 3 个数字,然后使用 & 来用 parentheses 包围替换这 3 个数字。

Using Multiple sed Commands

你可以使用以下方法在单个 sed 命令中使用多个 sed 命令 −

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

此处 command1commandN 为前面讨论过的类型 sed 命令。这些命令应用于给定文件中列表中的每一行。

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

$ 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

Note − 在上述示例中,我们用 {3} 替换了重复字符类关键字 [id=":digit:"] 三次,这意味着前面正则表达式匹配了三次。我们还使用了 \ 来换行,并且在运行命令之前必须将其删除。

Back References

ampersand metacharacter 很有用,但更重要的是能够定义正则表达式中的特定区域。这些特殊区域可以用作替换字符串中的参考。通过定义正则表达式的特定部分,你可以使用特殊参考字符引用这些部分。

要执行 back references ,你必须先定义一个区域,然后引用该区域。要定义一个区域,你可以在每个感兴趣的区域周围插入 backslashed parentheses 。你用反斜杠包围的第一个区域由 \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

Note − 在上述示例中,括号内的每个正则表达式都将由 \1\2 等进行反向引用。我们在此处使用 \ 来换行。在运行命令之前,应该将其删除。