sed 是 Linux 中提供的一个外部命令,是一个行(hang)编辑器,也叫流编辑器,非交互式的对文件内容进行增删改查的操作,只能在命令行输入编辑命令、指定文件名,然后在屏幕查看输出。
它和文本编辑器又本质的区别了,单从名字上来看,行编辑器和文本编辑器,前者将文件中的行作为编辑对象,后者将文件作为编辑对象。
工作原理
sed 命令将当前处理的行读入模式空间进行处理,处理完把结果输出,并清空模式空间。然后再将下一行读入模式空间进行处理输出,以此类推,直到最后一行。还有一个空间叫保持空间,又称暂存空间,可以暂时存放一些处理的数据,但不能直接输出,只能放到模式空间输出。
这两个空间其实就是在内存中初始化的一个内存区域,存放正在处理的数据和临时存放的数据。
命令格式
sed [选项] '匹配行 操作动作' file
选项
选项 | 描述 |
---|---|
-n | 仅显示处理后的结果 |
-e | 多重编辑,类似grep -e |
-f | 执行动作从文件读取 |
-i | 修改源文件 |
-r | 使用扩展正则表达式 |
操作动作
动作 | 描述 |
---|---|
/要打印输出的匹配行/p | 打印输出 |
s/要替换的字符/替换后的字符/ | 替换字符串 |
/匹配追定行/a \新增内容 | 在指定行后新增一行,a后面可以接字符串,接的字符串就是新增一行的内容 |
/要取代的匹配行/c \取代行内容 | 替换匹配行为指定内容 |
/要删除的匹配行/d | 删除,删除指定行 |
D | 删除第一行 |
/指定行/i \新增内容 | 在指定行之前新增一行,i后面可以接字符串,接的字符串就是新增一行的内容 |
: label | 定义标签 |
b label | 分支到脚本中带有标签的位置,如果分支不存在则分支到脚本的末尾 |
t label | 如果s///是一个成功的替换,才跳转到标签 |
h | 复制(覆盖)模式空间到保持空间 |
H | 追加模式空间到保持空间 |
I | 忽略大小写匹配 |
g | 复制(覆盖)保持空间到模式空间 |
G | 追加保持空间到模式空间 |
x | 交换模式空间和保持空间的内容 |
/匹配行/n | 读取匹配行的下一行到模式空间 |
/匹配行/N | 追加匹配行的下一行内容到模式空间,并以换行符\n分隔 |
l | 输出模式空间的行,并显示控制字符$ |
n N | 读取/追加下一行输入到模式空间 |
p | 将某个选择的数据输出,通常与-n选项一起使用 |
P | 输出第一行 |
q | 立即退出sed脚本 |
/指定行/r 文件名 | 指定行后追加到内容来自一个文件中的全部内容 |
/指定行/w 文件名 | 将匹配行的内容写到文件中 |
! | 取反、否定 |
& | 引用已匹配字符串 |
/匹配行/= | 输出匹配行的行号,使用$=可以获取文本中行的数量 |
匹配行
匹配行指令 | 描述 |
---|---|
1~2 | 步长,从第1行开始,每2行进行操作 |
$ | 匹配最后一行 |
2,5 | 从第2行开始匹配到第5行结束 |
1,+3 | 从第1行开始,向后3行进行操作 |
1,~3 | 从第1行开始,到3行结束 |
示例
示例文本
$ tail /etc/services
aigairserver 21221/tcp # Services for Air Server
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
spremotetablet 46998/tcp # Capture handwritten signatures
$ tail /etc/services > test.txt
以下所有的操作中加上选项 -i 则会直接修改文件内容,不加只会输出让你看到的内容变化,实际不修改文件内容。
输出打印(p)
# 输出以a开头的行
$ sed -n '/^a/p' test.txt
aigairserver 21221/tcp # Services for Air Server
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
# 输出第二行
$ sed -n '2p' test.txt
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
# 输出2-5行
$ sed -n '2,5p' test.txt
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
axio-disc 35100/tcp # Axiomatic discovery protocol
替换(s///)
默认会替换文件中所有的匹配字符
# 替换 ka-kdp 为 FeiYi
$ sed 's/ka-kdp/FeiYi/' test.txt
# 替换开头为 ka-kdp 的为 FeiYi,并只输出替换的行,-n和p结合使用
$ sed -n 's/^ka-kdp/FeiYi/p' test.txt
FeiYi 31016/udp # Kollective Agent Kollective Delivery
新增(a/i/c)
# 在 ka-kdp 下一行添加 test
$ sed '/ka-kdp/a \test' test.txt
aigairserver 21221/tcp # Services for Air Server
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
test
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
spremotetablet 46998/tcp # Capture handwritten signatures
# 在 ka-kdp 上一行添加 test
$ sed '/ka-kdp/i \test' test.txt
aigairserver 21221/tcp # Services for Air Server
test
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
spremotetablet 46998/tcp # Capture handwritten signatures
# 将有字符串 axio 的行替换为 test
$ sed '/axio/c \test' test.txt
aigairserver 21221/tcp # Services for Air Server
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
test
test
pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
spremotetablet 46998/tcp # Capture handwritten signatures
# 在指定行号下一行添加新一行
$ sed '1a \test' test.txt
aigairserver 21221/tcp # Services for Air Server
test
ka-kdp 31016/udp # Kollective Agent Kollective Delivery
ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery
edi_service 34567/udp # dhanalakshmi.org EDI Service
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API
cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive
cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
spremotetablet 46998/tcp # Capture handwritten signatures
删除(d)
# 删除带有 ka-kdp 的行
sed '/ka-kdp/d' test.txt
# 删除最后一行
sed '$d' test.txt
# 从第3行开始,每两行删一行
sed '3~2d' test.txt
# 删除第2行和第3行
sed '2,3d' test.txt
# 删除注释行和空行,例子中没有,可以随便给加一个
sed -i -e '5a \#' -e '9G' test.txt
sed -i -e '/^#/d' -e '/^$/d' test.txt
多重编辑(-e)
如上删除的例子中有 -e 的用法
有的地方说分号也可以达到-e的效果,如下,第一种可以,第二种不可以,我搞不懂,有大佬知道望告知,博主信息处有我的 QQ 联系方式。
sed '1,2d;3,4d' test.txt
sed '5a \#; 6a \#' test.txt
复制剪切粘贴操作(h/H/g/G/x)
h/H和g/G需要结合使用
# 复制ka-kdp一行,粘贴到cloudcheck-ping的一行并覆盖
sed -e '/ka-kdp/h' -e '/cloudcheck-ping/g' test.txt
# 剪切效果,{h;d}表示先复制,再删除
sed -e '/ka-kdp/{h;d}' -e '/cloudcheck-ping/g' test.txt
# 剪切并追加到最后
sed -e '/ka-kdp/{h;d}' -e '$G' test.txt
# 交换位置,复制ka-kdp一行到cloudcheck-ping的位置覆盖,然后将cloudcheck-ping放到文本末尾
sed -e '/ka-kdp/h' -e '/cloudcheck-ping/x' -e '$G' test.txt
空行(G)
单独使用G时,可用作添加空行
# 第一行后添加空行
sed '1G' test.txt
# 匹配行后添加空行
sed '/cloud/G' test.txt
忽略大小写匹配(I)
# s///g 是替换的操作动作,I是忽略大小写进行匹配,无论大小写的a都会被替换为1
sed 's/A/1/Ig' test.txt
读取下一行(n和N)
n:读取下一行到模式空间
# 输出匹配行的下一行,匹配到的第一行的下一行,并不会进行全局匹配
$ seq 5 | sed -n '/3/{n;p}'
4
# 输出偶数行,读取第一行的1,执行n输出下一行的2,执行p打印出,读取3,执行n输出下一行的4,一次类推
$ seq 6 | sed -n 'n;p'
2
4
6
# 输出奇数行,原理是将偶数行删除后的输出
$ seq 6 | sed 'n;d'
1
3
5
$ seq 6 | sed -n 'p;n'
1
3
5
N:追加下一行内容到模式空间,并以换行符\n分隔
可以理解为,N可以将两行看成一组
# 使用q可以在N执行玩一次之后退出
$ seq 6 |sed 'N;q'
1
2
# 原理:
# sed读取第一行,N读取下一行,此时为1\n2,sed读取第三行,N读取第四行,此时为3\n4,类推
$ seq 6 | sed 'N;s/\n//'
12
34
56
# 原理:
# sed读取第一行,N读取下一行,此时为1\n2,s///将换行符\n替换为空,此时为12,类推
# 当结尾行号为奇数时,默认不输出
$ seq 5 | sed -n 'N;p'
1
2
3
4
# 原理:
# sed读取第一行,N读取下一行,此时为1\n2,sed读取第三行,N读取第四行,此时为3\n4
# sed读取第五行,N读取下一行,下一行并没有,所以sed会退出,不会再执行后面的p
# 输出结尾为奇数的行
$ seq 5 | sed -n '$!N;s/\n//;p'
12
34
5
# 原理:
# sed读取第一行,N读取下一行,此时为1\n2,sed读取第三行,N读取第四行,此时为3\n4
# sed读取第五行,此时$!N就发生了反应,$表示最后一行,!N表示不执行N
# 也就是不读取下一行,连起来就是读取到最后一行时不执行N,可以执行后面的p
打印和删除第一行(P/D)
P:打印第一行
D:删除第一行
说实话这里我没懂
# 输出奇数
$ seq 6 | sed -n 'N;P'
1
3
5
# 保留最后一行
$ seq 6 | sed 'N;D'
6
读取文件并追加到匹配行后(r)
$ cat 123
123
# 第三行后添加123文件中的内容
sed '3r 123' test.txt
# 在匹配行后追加123文件中的内容
sed '/axio/r 123' test.txt
将匹配行写到文件(w)
$ sed '/axio/w 1.txt' test.txt
$ cat 1.txt
axio-disc 35100/tcp # Axiomatic discovery protocol
axio-disc 35100/udp # Axiomatic discovery protocol
标签(:、b和t)
标签可以控制流,实现分支判断。
: label name 定义标签
b label 跳转到指定标签,如果没有该标签则跳到脚本末尾
t label 跳转到指定标签,前提是s///命令执行成功
seq 6 | sed ':a;N;s/\n/,/;b a'
# :a 定义标签名为a
# sed读取第一行内容1
# N 将第一行的下一行内容追加到第一行,并以\n分隔,此时第一行为1\n2
# s/\n/,/ 此时第一行为1,2
# b a 跳转到a标签,也就是从N开始
# 此时的内容为
# 1,2
# 3
# 4
# 5
# 6
# sed读取第一行内容1,2
# N 将第一行的下一行内容追加到第一行,并以\n分隔,此时第一行为1,2\n3
# s/\n/,/ 此时第一行为1,2,3
# b a 跳转到a标签,也就是从N开始
# 此时的内容为
# 1,2,3
# 4
# 5
# 6
# 以次类推,直到内容为1,2,3,4,5,6时,N无法再读取下一行结束
seq 6 | sed ':a;N;$!b a;s/\n/,/g'
# :a 定义标签名为a
# sed读取第一行内容1
# N 将第一行的下一行内容追加到第一行,并以\n分隔,此时第一行为1\n2
# $!b a 表示读取到最后一行时,不执行b a,还没到最后一行,所以执行b a
# sed读取第一行内容为1\n2
# N 将第一行的下一行内容追加到第一行,并以\n分隔,此时第一行为1\n2\n3
# b a
# sed读取第一行内容为1\n2\n3
###直到内容变为1\n2\n3\n4\n5\n6
# $!b a 不再执行b a,也就是不会再跳回a标签的操作了,才会往后执行
# s/\n/,/g,将所有内容的\n替换为,
实例
# b1fffef17c17 变为 b1ff:fef1:7c17
$ echo "b1fffef17c17" | sed -r ':label;s/([^:]+)([0-9a-f]{4})/\1\:\2/;t label'
b1ff:fef1:7c17
# :label 定义标签
# s/([^:]+)([0-9a-f]{4})/\1\:\2/
# [^]是排除的意思
# ([^:]+),本条语句中排出了冒号不匹配,也可以理解为以冒号为分隔符进行匹配,遇到冒号就当作分隔符
# 冒号后作为第二行
# ([[0-9a-f]]{4}) 连续的4个字符为数字或者小写字母
# \1:\2 类似脚本的位置变量,在sed中表示为子匹配项的位置,本语句中([^:]+)为\1,([0-9a-f]{4})为\2
# 根据以下得出 \1:\2 = ([^:]+):([0-9a-f]{4}) = b1fffef1:7c17
$ echo "b1fffef17c17" | sed -rn 's/([^:]+)([0-9a-f]{4})/\1:\2/p'
b1fffef1:7c17
# 根据以上又可以得出,([0-9a-f]{4})的值为7c17,([^:]+)的值为b1fffef1
# 结果相当于是 “字符串:4个字符”
# 因为\2的匹配项,指定了4个字符的匹配,所以优先给限制字符个数的语句中匹配是通过倒序来的
# 如果将([0-9a-f]{4})和([^:]+)的位置互换,结果如下:
$ echo "b1fffef17c17" | sed -rn 's/([0-9a-f]{4})([^:]+)/\1:\2/p'
b1ff:fef17c17
# 当使用标签循环时,第二次sed读取的字符串位b1fffef1:7c17
# 上面说到遇到冒号就当作第二行来读取,sed读取就变成了
b1fffef1
7c17
# 很明显第二行不匹配([^:]+)([0-9a-f]{4})这样的正则,该正则至少5个字符串
# 所以只有b1fffef1符合特征,执行\1:\2的替换即可,同理得出
$ echo "b1fffef1" | sed -rn 's/([^:]+)([0-9a-f]{4})/\1:\2/p'
b1ff:fef1
# 经过循环得出的结果为b1ff:fef1:7c17
获取行号
# 获取axio所在行的行号
$ sed -n '/axio/=' test.txt
5
6
# 获取文本总行数
$ sed -n '$=' test.txt
5