作为 Linux 三剑客之一的 sed,shell 编程中掌握它是很有必要的。
本文用实验的方式循序渐进地学习 sed,可谓最科学的学习方式,一点一点遍历 sed 所有知识,实现真正的精通,同时还能作为速查。
快来动手跟着一起做吧!
入门实践
边实践边学习是真正掌握技能的唯一途径,下面从易到难来实验 sed 的各个命令。
实验准备
为了更好的实验,在临时目录建立一个文本文件以便测试。
1 | cd /tmp |
创建一个文本文件 a.txt ,并写入十行
1 | for a in $(seq 1 1 10); do echo line$a; done > a.txt |
以下是 a.txt 的内容
1 | line1 |
开始实验
a 命令 —— 添加新行
在第 4 行的下面添加一行 newline
1 | sed '4anewline' a.txt |
输出结果
1 | line1 |
也可以一次性添加多行
1 | sed '4anewline1\nnewline2' a.txt |
输出结果
1 | line1 |
指定地址范围
, —— 范围分隔符
从第 4 行到第 8 行的所有行的下面分别添加一行 newline
1 | sed '4,8anewline' a.txt |
输出结果
1 | line1 |
+ —— 到接下来几行
从第 4 行到其后两行的每一行分别添加一行 newline
1 | sed '4,+2anewline' a.txt |
输出结果
1 | line1 |
~ —— 倍数匹配
从第 5 行到第一次遇到的 4 的倍数行的每一行分别添加一行 newline
1 | sed '5,~4anewline' a.txt |
输出结果
1 | line1 |
~ —— 步进
从第 5 行起,所有行号为 2 的倍数的行后面添加一行 newline
1 | sed '5~2anewline' a.txt |
输出结果
1 | line1 |
$ —— 最后一行
从第 4 行到最后一行的每一行分别添加一行 newline
1 | sed '4,$anewline' a.txt |
输出结果
1 | line1 |
! —— 范围取反
从第 4 行到第 8 行之外的所有行后面添加一行 newline
1 | sed '4,8!anewline' a.txt |
输出结果
1 | line1 |
// —— 正则表达式范围
在匹配到 line4 的行后面添加一行 newline
1 | sed '/line4/anewline' a.txt |
输出结果
1 | line1 |
在匹配到 line4 到第 6 行的行后面添加一行 newline
1 | sed '/line4/,6anewline' a.txt |
输出结果
1 | line1 |
i 命令 —— 插入新行
在第 4 行前插入一行 newline
1 | sed '4inewline' a.txt |
输出结果
1 | line1 |
d 命令 —— 删除
删除 4 到 8 行
1 | sed '4,8d' a.txt |
输出结果
1 | line1 |
c 命令 —— 行替换
将 4 到 8 行替换成 newline
1 | sed '4,8cnewline' a.txt |
输出结果
1 | line1 |
p 命令 —— 打印
打印一遍 4 到 8 行
1 | sed '4,8p' a.txt |
输出结果
1 | line1 |
只打印 4 到 8 行
1 | sed -n '4,8p' a.txt |
输出结果
1 | line4 |
从匹配到 “line4” 的行打印到匹配到 “ne6” 的行
1 | sed -n '/line4/,/e6/p' a.txt |
输出结果
1 | line4 |
= 命令 —— 打印行号
从匹配到 “line4” 的行打印到匹配到 “ne6” 的行号
1 | sed -n '/line4/,/e6/=' a.txt |
输出结果
1 | 4 |
s 命令 —— 正则替换
将第 4 行到第 8 行的 ne 替换成 on
1 | sed '4,8s/ne/on/' a.txt |
输出结果
1 | line1 |
s 命令最后跟个 g 表示替换该行所有匹配
1 | echo 'abcdabcd\nbcdebcde' | sed 's/b/x/g' |
输出结果为
1 | axcdaxcd |
而不加 g 的 s 只能替换每一行匹配到的第一个
1 | echo 'abcdabcd\nbcdebcde' | sed 's/b/x/' |
输出结果为
1 | axcdabcd |
正则表达式的实例
1 | sed '4,8s/\(line\)\([[:digit:]]\+\)/\2 - \1/' a.txt |
输出结果为
1 | line1 |
y 命令 —— 字符映射替换
将第 4 行到第 5 行按照字符映射 {e,i,l,n} -> {o,c,e,h} 进行替换
1 | sed '4,5y/eiln/oceh/' a.txt |
输出结果
1 | line1 |
r 命令 —— 行替换为文件内容
1 | 先写入一个 b.txt 文件作为测试文件 |
输出结果
1 | line1 |
{} —— 脚本块
在大括号内用分号隔开命令,可以实现执行多个命令
打印 3 到 4 行和 7 到 8 行
1 | sed -n '{3,4p;7,8p}' a.txt |
输出结果
1 | line3 |
如果每个命令的地址相同,那么也可以类似于像分配律一样的语法
1 | sed -n '4{p;=}' a.txt |
输出结果
1 | line4 |
连 s 命令都能包含,体会到替换前和替换后的不同
1 | sed -n '4{p;s/ne/on/;p;=}' a.txt |
输出结果
1 | line4 |
大括号可以嵌套
1 | sed -n '{4{p;=};8{=;p}}' a.txt |
输出结果
1 | line4 |
如果嵌套时,地址不同,则会取交集
-
sed -n '3,7{4,5p}' a.txt
line4 line51
2
输出结果输出结果1
2
3
2. ```shell
sed -n '4,7{3,6p}' a.txt1
2
3line4
line5
line6
h,H,g,G,x —— 空间交换相关
首先需要弄懂两个概念:模式空间、保持空间。
**模式空间:**sed 命令默认是一行一行处理的,每读取到一行,会放到模式空间里,然后执行脚本来处理模式空间的内容,处理完后会清空模式空间,并且读取下一行继续处理,如此循环直到行尾。模式空间相当于专门用于 sed 处理的字符串缓冲区。
**保持空间:**默认的 sed 是不会操作保持空间的,保持空间是专门为用户提供的空间,这几个命令可以操作保持空间,来实现更复杂的功能。
**h:**把模式空间内容覆盖到保持空间中
**H:**把模式空间内容追加到保持空间中
**g:**把保持空间内容覆盖到模式空间中
**G:**把保持空间内容追加到模式空间中
**x:**交换模式空间与保持空间的内容
将第 4 行和第 6 行复制到第 8 行后面。
1 | sed '{4h;6H;8G}' a.txt |
输出结果
1 | line1 |
n,N,P,D —— 模式空间操作
有了模式空间的概念后,可以重新理解一下前面 p、d、n 等命令的本质。
**p:**打印当前模式空间所有内容,追加到默认输出之后。
**P:**打印当前模式空间开端至\n的内容,并追加到默认输出之前。Sed并不对每行末尾\n进行处理,但是对N命令追加的行间\n进行处理,因为此时sed将两行看做一行。
**n:**命令简单来说就是提前读取下一行,覆盖模型空间前一行,然后执行后续命令。然后再读取新行,对新读取的内容重头执行sed。
**N:**命令简单来说就是追加下一行到模式空间,同时将两行看做一行,但是两行之间依然含有\n换行符,然后执行后续命令。然后再读取新行,对新读取的内容重头执行sed。此时,新读取的行会覆盖之前的行(之前的两行已经合并为一行)。
**d:**命令是删除当前模式空间内容(不再传至标准输出), 并放弃之后的命令,并对新读取的内容,重头执行sed。
**D:**命令是删除当前模式空间开端至\n的内容(不在传至标准输出), 放弃之后的命令,但是对剩余模式空间重新执行sed。
删除匹配 line4 行的下一行
1 | sed '/line4/{n;d}' a.txt |
输出结果
1 | line1 |
打印偶数行
1 | sed -n '{n;p}' a.txt |
输出结果
1 | line2 |
删除匹配到 line4 行和下一行
1 | sed '/line4/{N;d}' a.txt |
输出结果
1 | line1 |
打印奇数行
1 | sed –n '$!N;P' a.txt |
输出结果
1 | line1 |
每次提前读取三行,输出第一行
1 | sed -n '{N;N;P}' a.txt |
输出结果
1 | line1 |
b —— 标签跳转
**b:**跳转到用冒号开头表示的标签
用标签跳转的方式实现:将除了匹配 line4 的行其它行的 ne 换成 on
1 | sed '/line4/bend;s/ne/on/;:end' a.txt |
输出结果
1 | lion1 |
t —— 条件标签跳转
**t:**如果前一条命令执行成功,则跳转到用冒号开头表示的标签。
用条件标签跳转的方式实现:将除了匹配 line4 的行其它行的 ne 换成 on,4换成 F
1 | sed 's/4/F/;tend;s/ne/on/;:end' a.txt |
输出结果
1 | lion1 |
速查
以下内容使用谷歌翻译自 sed(1) - Linux man page 以及 sed --help,可供参考。
名称
sed - 流编辑器,用于过滤和转换文本
语法
sed [可选参数]… {脚本} [输入文件]…
如果未提供-e,-expression,-f或–file选项,则将第一个非可选参数用作要解释的sed脚本。 其余所有参数均为输入文件的名称; 如果未指定输入文件,那么将读取标准输入。
描述
Sed是流编辑器。 流编辑器用于对输入流(文件或来自管道的输入)执行基本的文本转换。 尽管在某种程度上类似于允许脚本编辑(例如ed)的编辑器,但sed通过仅对输入进行一次传递来工作,因此效率更高。 但是sed能够过滤管道中的文本,这使其与其他类型的编辑器特别有区别。
-n, --quiet, --silent
禁止自动打印模式空间
-e 脚本, --expression=脚本
添加“脚本”到程序的运行列表
-f 脚本文件, --file=脚本文件
添加“脚本文件”到程序的运行列表
–follow-symlinks
直接修改文件时跟随符号链接; 硬链接仍然会断开。
-i[扩展名], --in-place[=扩展名]
直接修改文件(如果指定扩展名则备份文件)
-c, --copy
在 -i 模式下直接修改文件且备份时,使用复制操作而不是重命名。 尽管这样做可以避免断开链接(符号链接或硬链接),但最终的编辑操作不是原子的。 这很少是理想的模式。 --follow-symlinks通常就足够了,并且更快,更安全。
-l N, --line-length=N
指定“l”命令的换行期望长度
–posix
关闭所有 GNU 扩展
-E, -r, --regexp-extended
在脚本中使用扩展正则表达式(为保证可移植性使用 POSIX -E)。
-s, --separate
将输入文件视为各个独立的文件而不是单个长的连续输入流
-u, --unbuffered
从输入文件读取最少的数据,更频繁的刷新输出
–help
打印帮助并退出
–version
输出版本信息并退出
命令语法
这只是sed命令的简要提要,以提示那些已经知道sed的人。 必须参考其他文档(例如texinfo文档)以获取更完整的描述。
: 标签
b 和 t 命令的标签
# 注释
注释会一直延伸到下一个换行符(或**-e **脚本片段的末尾)。
}
关闭 {}块 的括号的右半括号
=
打印当前行号
a 文本
在模式空间后追加文本,该文本的每个嵌入换行符前都有一个反斜杠。
i 文本
在模式空间前插入文本,该文本的每个嵌入换行符前都有一个反斜杠。
q [退出代码]
立即退出 sed 脚本而不处理任何其他输入,除非如果未禁用自动打印,将打印当前图案空间。 退出代码参数是GNU扩展。
Q [退出代码]
立即退出 sed 脚本,而不处理更多输入。 这是一个GNU扩展。
r 文件名
从文件中读取的文本追加到模式空间中。
R 文件名
从文件中读取的文本追加到模式空间中。 每次调用该命令都会从文件中读取一行。这是一个GNU扩展。
{
启动 {}块 的括号的左半括号
b [标签]
跳转到标签; 如果省略了标签参数,则跳转到脚本结尾。
t [标签]
如果上一条读取的输入行被 s/// 命令替换成功,则跳转至标签 ; 如果省略标签,则跳转到脚本结尾。
T [标签]
如果上一条读取的输入行没有被 s/// 命令替换成功,则跳转至标签 ; 如果省略标签,则跳转到脚本结尾。这是一个GNU扩展。
c 文本
将所选行替换为文本,该文本的每个嵌入换行符前都有一个反斜杠。
d
删除模式空间。 开始下一个循环。
D
删除模式空间中的第一个嵌入的行。 从下一个循环开始,但是如果模式空间中仍有数据,则跳过从输入中读取的操作。
h H
复制/追加模式空间到保持空间。
g G
复制/追加保持空间到模式空间。
x
交换保持空间和模式空间的内容。
l
以“视觉清晰”的形式列出当前行。
l 宽度
以“视觉清晰”的形式列出当前行,并以根据指定宽度的字符将其断开。 这是一个GNU扩展。
n N
将输入的下一行读取/追加到模式空间。
p
打印当前模式空间
P
打印当前模式空间直到第一个嵌入式换行符。
s/正则表达式/替代串/
尝试将正则表达式与模式空间进行匹配。 如果成功,则用替代串/替换该部分。替代串可以包含特殊字符 “&” 来表示匹配的模式空间部分,而特殊转义 \1 到 \9 则表示正则表达式中的相应匹配子表达式。
w 文件名
将当前模式空间写入文件。
W 文件名
将当前模式空间的第一行写入文件。 这是一个GNU扩展。
y/源串/目标串/
将模式空间中出现在源串中的字符翻译成目标串中的相应字符。
地址
sed命令可以不带地址,在这种情况下,将对所有输入行执行该命令。 具有一个地址,在这种情况下,该命令仅对与该地址匹配的输入行执行; 或使用两个地址,在这种情况下,将对所有输入行执行命令,这些输入行与从第一个地址开始一直延伸到第二个地址的所有行包括在内。 有关地址范围的三点注意事项:语法为“地址1,地址2”(即地址用逗号分隔); 即使地址2选择了较早的行,也将始终接受与地址匹配的行; 如果地址2是正则表达式,则不会针对地址1匹配的行进行测试。
在地址(或地址范围)之后,在命令之前,! 可以插入,它指定仅当地址(或地址范围)不匹配时才执行命令。
数字
仅匹配指定的数字行。
首行~步进
匹配从首行开始的每个相隔步进的行。 例如,“ sed -n 12p”将打印输入流中的所有奇数行,并且地址25将与第二行开始的每第五行匹配。首行可以为零; 在这种情况下,sed 的操作就好像等于步进。 (这是一个扩展。)
$
表示最后一行。
/正则表达式/
匹配正则表达式的行。
\c正则表达式c
匹配正则表达式的行。c 可以是任何字符。
GNU sed 还支持一些特殊的2地址形式
0,地址2
从“匹配的第一个地址”状态开始,直到找到地址2。 这类似于“1,地址2”,不同之处在于,如果地址2与输入的第一行匹配,则“0,地址2”形式将在其范围的末尾,而“1,地址2”形式仍为在其范围的开始。仅当地址2为正则表达式时有效。
地址1,+N
将匹配地址1和地址1之后的 N 行。
地址1,~N
将匹配地址1和地址1之后的行,直到输入行号是 N 的倍数的下一行。
正则表达式
本来应该支持POSIX.2 BRE,但是由于性能问题,它们并不完全支持。 正则表达式中的 \n 序列与换行符匹配,对于 \a,\t 和其他序列也是如此。