sed(Stream Editor)是一个流编辑器,它以逐行方式处理输入流(通常是文本文件),并根据指定的编辑命令进行相应的操作。

sed 的工作原理

  1. 逐行处理输入流: sed 逐行读取输入流,并对每一行应用相应的编辑命令。输入流可以是文件、管道输出等。
  2. 模式匹配: sed 使用模式来匹配文本中的特定部分。用户可以使用正则表达式或简单的字符串来定义匹配模式。
  3. 编辑命令: sed 使用编辑命令来对匹配到的文本进行操作。编辑命令可以是替换命令、删除命令、插入命令等,以及一些控制流程的命令。
  4. 应用编辑命令: 一旦匹配到了指定的文本,sed 就会根据相应的编辑命令执行相应的操作。这些操作可以是替换匹配文本、删除匹配行、插入新文本等。
  5. 输出结果: sed 在处理完所有输入流后,将结果输出到标准输出(通常是终端),或者通过 -i 参数直接在原文件上进行修改。

sed空间

sed 中,有两个特殊的空间:模式空间(pattern space)和保留空间(hold space)。这些空间允许在处理文本时存储和操作数据。

  1. 模式空间(pattern space): 模式空间是 sed 中用于存储当前处理的行的缓冲区。当 sed 读取一行文本时,文本会被存储在模式空间中,sed 在模式空间中对其进行匹配和操作。模式空间中的内容可以被修改、替换或者通过命令输出到标准输出。默认情况下,sed 在处理完每一行后会自动清空模式空间,然后读取下一行。

  2. 保留空间(hold space): 保留空间是另一个可用于存储数据的空间,与模式空间相互独立。可以使用保留空间来存储临时数据或在处理文本时进行比较。与模式空间不同,保留空间的内容不会随着处理的文本行而变化。

可以将数据从模式空间移动到保留空间,反之亦然。这使得 sed 能够在文本处理过程中灵活地使用和操作数据。

以下是一些 sed 命令,用于操作模式空间和保留空间中的数据:

  • h:将模式空间中的内容复制到保留空间。
  • H:将模式空间中的内容追加到保留空间的末尾。
  • g:将保留空间中的内容复制到模式空间。
  • G:将保留空间中的内容追加到模式空间的末尾。
  • x:交换模式空间和保留空间中的内容。

除了模式空间(Pattern Space)和保留空间(Hold Space)外,还有以下几个空间或概念:

  • 输入缓冲区(Input Buffer):这是 sed 从输入文件或标准输入读取当前行的地方。在读取一行后,sed 会将其放入模式空间中进行处理。

  • 输出缓冲区(Output Buffer):在默认情况下,sed 会将处理后的模式空间内容立即输出。但是在某些情况下(如使用 -n 选项时),输出会被暂存,直到明确调用 p 或其他输出指令,或者处理结束时才会输出。

  • 命令行缓冲区(Command Line Buffer)sed 脚本中的命令会被暂存到这个缓冲区中,随后按顺序应用于模式空间的内容。每一行在处理完毕后,sed 会继续读取下一行并重复这个过程。

  • 地址范围和上下文范围(Address and Context Range Buffers):当指定一个地址范围或上下文范围(如 sed '/start/,/end/')时,sed 会记住当前行是否在该范围内,以决定是否应用接下来的命令。

sed命令格式

sed [选项] ‘编辑命令’ 文件名

常用的选项包括:

  • -n:关闭自动打印模式,只有经过编辑命令处理后的行才会输出。
  • -e:指定一个或多个编辑命令。
  • -f:从指定文件中读取编辑命令。

编辑命令是对文本进行操作的指令,可以是替换、删除、插入、打印等操作,常见的编辑命令包括:

  • s/old/new/g:替换行中的 old 字符串为 new
  • /pattern/d:删除匹配到的行。
  • /pattern/i\text:在匹配到的行之前插入文本。
  • /pattern/a\text:在匹配到的行之后追加文本。
  • /pattern/p:打印匹配到的行。

sed指令

替换 s

s/old/new/g:将行中所有的 old 替换为 new。其中 g 表示全局替换,如果不加 g,则只替换每行的第一个匹配。
除了g标志,还可以是具体数字:s/old/new/2,表示替换第2个。
p标志打印替换的行:s/old/new/p。
w标志可以将更改的行保存的文件中:s/old/new/w file.txt。
i标志表示不区分大小写:s/old/new/i
例如:sed 's/foo/bar/g' filename:将文件中所有的 foo 替换为 bar

s命令除了可以使用/作为分隔符,还可以使用**| @ ! ^**作为分隔符

字符替换标志:

\L: 将紧随其后的所有字符转换为小写,直到遇到 \E 或字符串结束。

\U: 将紧随其后的所有字符转换为大写,直到遇到 \E(结束转换)或字符串结束。

\l: 将紧随其后的第一个字符转换为小写。

\u: 将紧随其后的第一个字符转换为大写。

\E:用于结束 \L\U 的作用范围。

删除d

  • /pattern/d:删除匹配到的行。
  • 例如:sed '/pattern/d' filename:删除所有包含 pattern 的行。

插入i \

  • /pattern/i\text:在匹配到的行之前插入指定文本。
  • 例如:sed '/pattern/i\new_text' filename:在包含 pattern 的行之前插入 new_text

追加a \

  • /pattern/a\text:在匹配到的行之后追加指定文本。
  • 例如:sed '/pattern/a\new_text' filename:在包含 pattern 的行之后追加 new_text

打印p

  • /pattern/p:打印匹配到的行。
  • 例如:sed -n '/pattern/p' filename:仅打印包含 pattern 的行。

替换y

  • y/chars1/chars2/:将文本中 chars1 中的字符替换为 chars2 中对应位置的字符。
  • 例如:sed 'y/abc/123/' filename:将文件中所有的 a 替换为 1b 替换为 2c 替换为 3

其他指令

行范围命令 (start,end):

start,end\<命令>:对指定行范围内的所有行执行指定的命令。

例如:sed '2,4{s/foo/bar/g}' filename:对第 2 行到第 4 行之间的所有行,将所有的 foo 替换为 bar

行范围(start,+num):

从start开始的,num行

行范围(start~num):

从starjt行开始,每隔num行。注意和**(start,~num)**的区别

/pattern/,/pattern/或者start,/pattern/ 也可以混合地址和模式**/pattern/,+num**

!命令

用于反转命令的作用范围,它让命令仅对不匹配的行生效。

seq 1 10 | sed -n '1,5p', 输出1-5行。

seq 1 10 | sed -n '1,5!p', 输出除1-5行之外的行。

行号 num

  • n:对指定行号的行执行指定的命令。
  • 例如:sed '2{s/foo/bar/g}' filename:对第 2 行,将所有的 foo 替换为 bar

替换行命令(c)

s 命令不同,c 命令不是进行模式替换,而是用新文本完全替换匹配的整行内容。非常适合用于替换整行内容,而不是在行内做部分替换。

显示隐藏字符(l)

显示非打印字符为其控制符。

1
2
sed -n 'l' wawa.txt
sed -n 'l 5' wawa.txt #显示了个字符后换行

退出当前执行流程(q)

注意:quit 命令不接受地址范围,它仅支持单个地址。默认情况下,SED 遵循读取、执行和重复工作流程;但是当遇到quit命令时,它只是停止当前的执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@MiWiFi-RA80-srv test]# sed '3q' wawa.txt 
name wawa
age 6
name feizi

# 还可以设置退出状态码
[root@MiWiFi-RA80-srv test]# sed '4q 10' wawa.txt
name wawa
age 6
name feizi
age 30
[root@MiWiFi-RA80-srv test]# echo $?
10

标签命令 (:):

跳转命令 (b):

转换命令 (y):

执行命令 (e):在匹配的行上执行指定的shell命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在2,3,4,5行后附加whoami的输出内容
[root@MiWiFi-RA80-srv test]# sed '2,5e whoami' wawa.txt
name wawa
root
age 6
root
name feizi
root
age 30
root
name beizi
age 36
name zs
age 30

打印行号命令 (=):

  • =:打印当前行号。将行号及其内容写入标准输出流
  • 例如:sed -n '=' filename:打印文件中每行的行号。
  • sed -n '$ ='filename打印文件最后一行行号,也就是文件总共多少行

&命令

SED 支持特殊字符 &。每当模式匹配成功时,此特殊字符就会存储匹配的模式。它经常与替换命令一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@MiWiFi-RA80-srv test]# cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed 's/[[:digit:]]/Number &/' a
Number 1 John 123456789
Number 2 Alice 987654321
Number 3 Bob 456789123
Number 4 Jerry 23209009
Number 5 Bily 230004044
Number 6 Civi 2223394494949

命令 (N):

N 命令将下一行读取到模式空间中,并将它追加到当前模式空间的内容后面(用换行符分隔),使得模式空间包含多行文本。

执行N:

  • 当前模式空间的内容不会被清除。

  • 读取下一行并将其追加到现有的模式空间内容后面。

  • 模式空间现在包含两行或更多行的内容,可以对其进行进一步的处理。

  • 例如:sed 'N;s/foo\nbar/barfoo/' filename:将匹配到 foobar 位于不同行的两行合并为一行,并将其替换为 barfoo

1
2
3
4
5
6
7
[root@testserver ~]# echo -e "a\naaa\nb\nc\naaaaaaa\naaa\ne" |sed 'N;s/a\+\na\+/@/'
@
b
c
@
e

默认情况下,SED 在单行上运行,但它也可以在多行上运行。多行命令用大写字母表示。例如,与 n 命令不同,N 命令不会清除并打印模式空间。相反,它会在当前模式空间的末尾添加换行符 (\n),并将输入文件中的下一行附加到当前模式空间,并通过执行其余的 SED 命令继续 SED 的标准流程。

n命令

n 命令打印模式缓冲区的内容,清除模式缓冲区,将下一行提取到模式缓冲区中,然后对其应用命令。

n 命令会跳过当前行的进一步处理,直接读取下一行并用它替换模式空间中的内容,然后开始处理新行。

执行n命令后:

  • 当前模式空间的内容会被清除。
  • 读取下一行并将其放入模式空间。
  • 从读取的新行开始执行剩余的 sed 脚本命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@MiWiFi-RA80-srv test]# cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'n;n;p' a
3 Bob 456789123
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'x;n;p' a #打印偶数行,同 sed -n 'n;p'
2 Alice 987654321
4 Jerry 23209009
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'x;n;x;p' a #打印奇数行,同 sed -n 'p;n'
1 John 123456789
3 Bob 456789123
5 Bily 230004044

打印模式空间命令 (p):

  • /pattern/,+1p:打印匹配到的行及其下一行。
  • 例如:sed -n '/pattern/,+1p' filename:打印包含 pattern 的行及其下一行。

读写文本命令 (rw):

  • r filename:在模式空间中的行后面追加指定文件中的内容。
  • w filename:将模式空间中的内容写入到指定文件中。
  • 例如:sed '/pattern/r file.txt' filename:在匹配到 pattern 的行后追加 file.txt 文件中的内容。

打印模式空间第一行(P)

与 p 命令类似, P 命令来打印由 N 命令创建的多行模式空间的第一部分(直到嵌入换行符)。如果没有有N,它和p命令相同。

命令(D )

D 命令会删除模式空间中的第一行,并重新开始处理剩余的模式空间。如果剩余的模式空间不为空,D 命令会跳转到脚本的开头重新执行,直到模式空间为空为止。

这种行为允许 sed 脚本在处理多行文本时实现循环处理的效果,从而允许对整个文件进行逐行处理而不是一次性处理整个文件。

1
2
sed '$!N; /^\(.*\)\n\1$/!P;D' filename
# 删除连续重复行

检查版本(v)

SED 还提供了一个 v 命令来检查版本。如果提供的版本高于安装的 SED 版本,则命令执行失败。请注意,此选项是 GNU 特定的,可能不适用于 SED 的其他变体。

h命令

h 命令处理保持缓冲区。它将数据从模式缓冲区复制到保持缓冲区。保持缓冲区中的现有数据将被覆盖。请注意,h 命令不会移动数据,它只会复制数据。因此,复制的数据在模式缓冲器中保持原样。

1
2
3
4
5
6
7
8
9
[~/test]: cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[~/test]: sed -n '/Bob/!h;/Bob/{x;p}' a
2 Alice 987654321

H命令

SED 提供了 H 命令,该命令通过在末尾添加新行将内容附加到保持缓冲区。 h 和 H 命令之间的唯一区别是,前者覆盖保持缓冲区中的数据,而后者将数据追加到保持缓冲区。

1
2
3
[~/test]: sed -n '/Bob/!h;/Bob/{H;x;p}' a
2 Alice 987654321
3 Bob 456789123

g命令

将数据从保持缓冲区复制到模式缓冲区。复制时,模式空间中的现有数据会被覆盖。

1
2
3
[~/test]: sed -n '/Bob/!h;/Bob/{p;g;p}' a
3 Bob 456789123
2 Alice 987654321

G命令

将保持缓冲区的内容附加到模式缓冲区。 SED 提供 G 命令,通过在末尾添加新行将内容附加到模式缓冲区。

1
2
3
4
5
6
7
8
9
10
11
12
13
[~/test]: cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[~/test]: sed -n '/Bob/!h;/Bob/{G;p}' a
3 Bob 456789123
2 Alice 987654321
[~/test]: sed -n '/Bob/!h;/Bob/{x;G;p}' a
2 Alice 987654321
3 Bob 456789123

x命令

交换模式空间和保持空间的内容

实例

有一个employee.txt的员工信息表,内容如下:

1
2
3
4
5
6
Employee ID, Name, Department, Salary
001, Zhan San, Marketing, 5000
002, Li Si, Sales, 5500
003, Wang Wu, IT, 7500
004, Liu Liu, HR, 6000
005, Wang Qi, Finance, 7000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 除第一行末尾添加$符号
[root@testserver ~]# sed '1!s/$/$/' employee.txt

# 将每个单词首字母改为小写
[root@testserver ~]# sed 's/\b\([A-Z]\)/\l\1/g' employee.txt

# 将所有字母改为大写
[root@testserver ~]# sed 's/\(.*\)/\U\1/g' employee.txt

# 行首显示行号
[root@testserver ~]# sed = employee.txt | sed 'N;s/\n/ /'

# 也可以在sed命令中使用变量实现动态处理,这时候指令使用双引号
[root@testserver test]# pa="^[0-9]"
[root@testserver test]# sed -n "/$pa/p" employee.txt

# 把Li Si的薪资前加上$单位
[root@testserver test]# sed '/Li Si/ s/[[:digit:]]\+$/$&/' employee.txt

# 可以利用分组捕获进行某种处理
sed 's/^\([0-9]\+\)\([^0-9]\+\)\([0-9]\+\)$/\3\2\1/' employee.txt
# 对比与上面的区别
sed -n 's/^\([0-9]\+\)\([^0-9]\+\)\([0-9]\+\)$/\3\2\1/p' employee.txt
1
2
3
4
5
6
# 递归处理目录下的多个文件,可以使用find结合sed

find . -type f -name "*.txt" -exec sed -i 's/foo/bar/g' {} +

# 如果不递归查找,只查找1级目录,则用-maxdepty选项
find . -maxdepth 1 -type f -name "*.txt" -exec sed -i 's/foo/bar/g' {} +

find 命令中,-exec 选项后面可以跟随一个命令来执行。{} 表示 find 命令找到的文件名列表。; 表示命令的结尾,每找到一个文件就执行一次命令。+ 则表示将找到的文件名作为参数传递给一个单独的命令,而不是每个文件都执行一次命令。

使用 + 可以有效地减少 sed 命令的调用次数,提高效率,因为 sed 命令一次可以处理多个文件。相比之下,使用 ; 每次只能处理一个文件,可能会导致 sed 命令被调用多次,性能较低。

如果你使用 \ 替代 +,即 {} \;,则相当于告诉 find 命令每次找到一个文件就执行一次 sed 命令。这种方式的效率较低,因为 sed 命令会被频繁地调用,不如使用 + 效率高。

1
2
3
4
5
6
7
8
[root@testserver test]# cat a
1 John 123456789
2 Alice 987654321

3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
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
32
33
34
# 删除空行
sed '/^$/d' a

# 反转行
sed '1!G;h;$!d' a

[root@testserver test]# rev a
987654321 nhoJ 1
123456789 ecilA 2

321987654 boB 3
90090232 yrreJ 4
440400032 yliB 5
9494944933222 iviC 6

# 配合rev反转文件内容
[root@testserver test]# sed '1!G;h;$!d' a |rev
9494944933222 iviC 6
440400032 yliB 5
90090232 yrreJ 4
321987654 boB 3

123456789 ecilA 2
987654321 nhoJ 1

# 统计行数,其实是输出最后一行的行号
[root@testserver test]# sed -n '$=' a
7

# 输出匹配行的行号
[root@testserver test]# sed -n '/Bob/=' a
4