参考书籍:《Linux Shell核心编程指南》——丁明一

一、概述

sed是贝尔实验室的Lee E.McMahon在1973年到1974年开发的流编辑器,sed是基于交互式行编辑器ed开发的软件,是一款行处理编辑器

二、sed基本指令

sed会逐行扫描输入的数据,并将读取的数据内容复制到模式空间,然后拿模式空间中的数据与给定的条件进行匹配,如果匹配成功则执行特定的sed指令,否则跳过输入的数据行,继续读取后面的数据。

默认情况sed会把最终的数据结果通过标准输出显示在屏幕上
在这里插入图片描述

2.1.sed语法格式

命令 | sed [选项] ‘匹配条件和操作指令’

sed [选项] ‘匹配条件和操作指令’ 输入文件

2.2.命令选项

命令选项功能描述
-n屏蔽默认输出功能,默认sed会把匹配到的数据显示在屏幕上
-r支持扩展正则
-i[SUFFIX]直接修改源文件,如果设置了SUFFIX后缀名,sed会将数据备份
-e指定需要执行的sed指令,支持使用多个-e参数
-f指定需要执行的脚本文件,需提前将sed指令写入文件中

2.3.基本操作指令

操作指令功能描述
p打印当前匹配的数据行
l小写L,打印当前匹配的数据行(显示控制字符,如回车符等)
=打印当前读取的数据所在的行数
a text在匹配的数据行后追加文本内容
i text在匹配的数据行前插入文本内容
d删除匹配的数据行整行内容(行删除)
c text将匹配的数据行整行内容替换为特定的文本内容
r filename从文件中读取数据并追加到匹配的数据行后面
w filename将当前匹配到的数据写入特定的文件中
q [exit code]立刻退出sed脚本
s/regexp/replace/使用正则匹配,将匹配的数据替换为特定的内容

2.4.sed支持的数据定位方法

格式功能描述
number直接根据行号匹配数据
first~step从first开始,步长为step,匹配所有满足条件的数据行
$匹配最后一行
/regexp/使用正则表达式匹配数据行
\cregexpc使用正则表达式匹配数据行,c可以是任意字符
addr1,addr2直接使用行号定位,匹配从addr1到addr2的所有行
addr1,+N使用行号定位,匹配从addr1开始及后面的N行

2.5.示例

p指令

sed 'p' /etc/hosts

在这里插入图片描述
没有指定条件时,默认匹配所有数据行
sed读取文件的第1行执行p指令将该行内容显示在屏幕上,接着读取文件的第2行继续执行p指令将该行内容显示在屏幕上。但为什么每行数据会显示两次呢?
因为即使没有p指令,sed也默认将读取到的所有数据显示在屏幕上

可以使用-n选项屏蔽sed默认的输出功能
在这里插入图片描述

# sed -n '1p' /etc/hosts	#仅显示第1行
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
# df -h | sed -n '2p'		#支持从管道读取数据
/dev/mapper/centos-root   17G  7.6G  9.5G   45% /

直接使用行号匹配

# cat -n /etc/passwd > /tmp/passwd	#生成带行号的素材文件
# sed -n '1,3p' /tmp/passwd			#显示文件的第1到第3行
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
# sed -n '1p;3p' /tmp/passwd			#显示第1行和第3行
     1  root:x:0:0:root:/root:/bin/bash
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
# sed -n '29,$p' /tmp/passwd			#第29行到末尾所有行
    29  xiaoliu:x:1010:1010::/home/xiaoliu:/bin/bash
    30  zhangsi:x:1011:1011::/home/zhangsi:/usr/sbin/nologin
    31  dockerroot:x:997:995:Docker User:/var/lib/docker:/sbin/nologin
# sed -n '1,+2p' /tmp/passwd			#显示第1行及后面的两行
# sed -n '1~2p' /tmp/passwd				#显示1,3,5...奇数行(步长为2)
# sed -n '$p' /tmp/passwd				#显示文件最后一行

使用正则表达式匹配

# sed -n '/root/p' /tmp/passwd			#匹配包含root的行
# sed -n '/bash$/p' /tmp/passwd			#匹配以bash结尾的行
# sed -n '/s...:x/p' /tmp/passwd		#匹配以s开头,以:x结尾,中间包含任意三个字符
# sed -n '/[0-9]/p' /tmp/passwd			#匹配包含数字的行
# sed -n "/^docker/p" /etc/passwd		#显示以docker开头的行

默认sed不支持扩展正则,如果希望使用扩展正则匹配数据,可以使用-r参数

# sed -rn '/^(icmp|igmp)/p' /etc/protocols
icmp    1       ICMP            # internet control message protocol
igmp    2       IGMP            # internet group management protocol
# sed -n '\cUIDcp' /etc/login.defs	#匹配包含UID的行,等价于sed -n '/UID/p' /etc/login.defs
UID_MIN                  1000
UID_MAX                 60000
SYS_UID_MIN               201
SYS_UID_MAX               999
# sed -n '\xbashxp' /etc/shells		#匹配包含bash的行,x可以是任意字符
# sed -n 'l' /etc/shells			#显示数据内容时打印控制字符
/bin/sh$
/bin/bash$

使用=指令显示行号

# sed -n '/root/=' /etc/passwd	#显示包含root字符串的行号
1
10
# sed -n '$=' /etc/passwd		#显示最后一行的行号

使用感叹号(!)对匹配的条件取反

# sed -n '1!p' /etc/hosts			#显示除第1行外的所有行数据
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# sed -n '/bash/!p' /etc/passwd		#除bash外的所有行都显示

a指令和i指令

sed '1a hello world' /tmp/hosts	#在第1行后面添加1行数据

在这里插入图片描述
以上方法不会修改源文件,如果希望修改源文件,需使用-i选项

sed -i '1a hello world' /tmp/hosts

在这里插入图片描述

sed -i.bak '2d'  /tmp/hosts		#先备份原文件为/tmp/hosts.bak,再删除第2行

在这里插入图片描述

sed -i '1i add new line' /tmp/hosts		#在第一行前面插入数据
sed '/new/a temp line' /tmp/hosts		#在匹配包含new的行后面添加数据

在这里插入图片描述

c指令

sed '1c modify line' /tmp/hosts	#将第1行整行替换为modify line

d指令

sed 'd' /tmp/hosts		#删除全文
sed -n '$d'  			#删除最后一行
cp /etc/profile /tmp/	
sed -i '/^$/d' /tmp/profile		#删除空白行
sed -i '/^#/d' /tmp/profile		#删除以#号开头的行

s指令,替换

准备素材文件:

# cat > /tmp/test.txt << EOF
hello the world.
go spurs go.
123 456 789.
hello the beijing.
I am Devops.
EOF
sed 's/hello/hi/' /tmp/test.txt		#将每行的第一个hello替换为hi
sed 's/o/O/g' /tmp/test.txt			#将每行所有的o替换为O

在这里插入图片描述

sed 's/o/O/2' /tmp/test.txt		#仅替换每行的第2个字母o

在这里插入图片描述
添加i标记

sed 's/devops/test/i' /tmp/test.txt		#在s替换指令的最后添加i标记可以忽略大小写

添加e标记

# echo "/etc/hosts" | sed 's/^/ls -l /e'
-rw-r--r--. 1 root root 158 67 2013 /etc/hosts
# echo "tmpfile" | sed 's#^#touch /tmp/#e'

# ls -l /tmp/tmpfile
-rw-rw-r-- 1 root root 0 612 03:45 /tmp/tmpfile

在s指令后面添加e标记,表示将替换后的内容当成shell命令在终端执行一次。
第一条sed命令是把/etc/hosts替换为ls -l /etc/hosts
第二条sed命令是把tmpfile替换为touch /tmp/tmpfile

sed 's/the//' /tmp/test.txt		#将the替换为空,即删除

多个双引号的问题

# echo '"hello" "world"' | sed 's/\".*\"//'

# echo '"hello" "world"' | sed 's/\"[^\"]*\"//'
 "world"

第一个替换是匹配双引号开头双引号结尾和中间所有数据,并删除
第二个替换仅仅匹配由一个引号开始,中间不包含引号的任意其他字符,最后是一个双引号结束的数据

sed保留

# sed -r 's/^(.)(.*)(.)$/\3\2\1/' /tmp/test.txt		#将每行首尾字符对调
.ello the worldh
.o spurs gog
.23 456 7891
.ello the beijingh
. am DevopsI

上面这条命令应用了正则表达式的保留功能,使用圆括号将匹配的数据保留,然后通过\n来调用前面保留的数据。\1表示第一个括号保留的数据,依次类推。

替换符号

sed -n 's#/sbin/nologin#/bin/sh#p' /tmp/passwd

s指令默认使用斜线(/)作为替换符号,但当替换内容本身包含斜线时需要转义,使得代码不美观,因此可以用其他字符作为替换符。
上面的命令则是用#作为替换符

在正则匹配到的内容后面追加内容
\n代表换行符

sed -i 's/^touch.*/&\nsysctl -p\nulimit -SHn 65535/' /etc/rc.d/rc.local

在这里插入图片描述

常用:

sed -r 's/^ +//g'   		 		 #删除每行前面的空格
sed -i "10s/^/ /" filename			 #在第10行前面添加4个空格
sed 's/unix/linux/2g' test.txt       #替换每一行中从第二个开始的unix
sed 's/.//2;s/.$//'     	 		 #删除第二个字符和最后一个字符
sed 's/ *$//' example.txt    		 #删除每一行最后的空白字符
sed 's/,$//' example.txt			 #删除每行最后一个逗号
sed -i '/^abc/,$d' t1.txt			 #删除匹配行到最后的行
sed '1,3 s/\bunix\b/linux/' test.txt       		#只替换完整匹配的unix,\b代表单词边界
sed -n 's/\b[0-9]\{2\}\b/number/gp' test.txt	#将两位数的数字替换为number
sed -i  "s|image: .*|image: nginx:1.0|g" web.yaml		#替换镜像为nginx:1.0

r指令,从文件中读取数据

准备两个文件hostsips.txt,把ips.txt中的内容放到[web]下面
在这里插入图片描述

sed -i "/\[web\]/r ips.txt" hosts

在这里插入图片描述

w指令,将匹配的数据写入到新文件中

sed -n '1,2w /tmp/myhosts' /tmp/hosts	#把第1行和第2行的数据另存为新文件tmp/myhosts

在这里插入图片描述

q指令,退出sed

# sed '2q' /etc/shells		#读取到第2行时退出
/bin/sh
/bin/bash

注意:避免与-i选项同时使用,否则会导致sed读取出来的数据覆盖源文件

编写多条指令

使用以下三种方法可以同时编写多条sed指令

sed -e 's/static/dhcp/g' -e 's/yes/no/g' /tmp/test.txt    #使用-e选项
sed 's/static/dhcp/g;s/yes/no/g' /tmp/test.txt     		  #使用分号隔开
sed '                                                     #利用分行
> s/yes/no/g
> s/static/dhcp/g' /tmp/test.txt

指令分组:
将分号放到花括号中可以实现对指令的分组

# sed '/world/s/hello/hi/;s/the//' /tmp/test.txt	#不使用分组
hi  world.
go spurs go.
123 456 789.
hello  beijing.
I am Devops.
# sed '/world/{s/hello/hi/;s/the//}' /tmp/test.txt	#使用分组
hi  world.
go spurs go.
123 456 789.
hello the beijing.
I am Devops.

不使用分组时,先找到包含world的行,将hello替换为hi,因为直接使用分号分隔多条指令,匹配条件仅对第一个指令有效,第二个指令在没有匹配条件的情况下会匹配所有数据

使用分组的最大好处就是在满足匹配条件时执行一组命令,此时匹配条件会对分组中的所有指令有效,因此上面第二条命令只会删除包含world数据行的the

-f选项,读取指令文件

编辑指令文件
vi script.sed

1c hello world
2 {
    p
    s/g/G/
}
/[0-9]/d

通过-f调用指令文件

# sed -f script.sed /tmp/test.txt
hello world
go spurs go.
Go spurs go.
hello the beijing.
I am Devops.

上述指令含义:将第一行替换为hello world;匹配第二行,先显示数据,再将第一个g替换为G;使用正则删除所有包含数字的行

三、sed高级指令

在这里插入图片描述
sed在对数据进行编辑修改前需要先将读取的数据写入到模式空间中,sed还设计了一个保留空间,默认仅包含一个回车符。
在这里插入图片描述
h指令,把模式空间中的内容复制到保留空间,并覆盖保留空间中的回车符
H指令,把模式空间中的内容复制到保留空间,并不覆盖保留空间中的回车符
g指令,把保留空间的内容复制到模式空间,模式空间原有的数据被覆盖
G指令,把保留空间的内容追加到模式空间,模式空间原有的数据不被覆盖
x指令,将模式空间与保留空间中的数据直接交换

3.1.示例

准备素材文件
vi test.txt

1:hello the world.
2:go spurs go.
3:123 456 789.
4:hello the beijing.
5:I am Devops.

g、h、x指令

sed '1h;4g' test.txt

在这里插入图片描述
读取第1行数据,放入到保留空间,且覆盖回车符;在读取第4行数据时,将保留空间的数据覆盖掉模式空间中的数据(结果就是第1行数据替换了原来第4行的数据)

sed '1H;4G' test.txt

在这里插入图片描述

sed '1h;1d;4g' test.txt

在这里插入图片描述
将第1行剪切到了文件的第4行

sed '1h;2H;4x' test.txt

在这里插入图片描述
先把第1行和第2行放入保留空间,当读取第4行时,将保留空间的数据与模式空间互换

n指令

sed遇到n指令会立刻输出当前模式空间的内容,直接读取文件的下一行数据到模式空间

sed 'n;d' test.txt		#删除偶数行

在这里插入图片描述
读取第一行到模式空间,执行n指令输出当前模式空间的内容,直接读取下一行到模式空间,此时再执行d就会删除第2行,以此类推。

# sed -n '2{N;p}' test.txt
2:go spurs go.
3:123 456 789.

读取第2行到模式空间后,使用N读取第3行追加到模式空间现有数据后面,此时模式空间有两行数据

# sed 'N;s/\n//' test.txt		#每两行合并为一行
1:hello the world.2:go spurs go.
3:123 456 789.4:hello the beijing.
5:I am Devops.

y指令

以字符为单位替换,单个字符是sed中的最小处理单位

# sed 'y/hg/HG/' test.txt		#h替换为H,g替换为G
1:Hello tHe world.
2:Go spurs Go.
3:123 456 789.
4:Hello tHe beijinG.
5:I am Devops.

标签功能

当有多个sed指令时默认按顺序执行,如果要打破这种限制,按照我们希望的顺序执行,则可以使用sed提供的标签功能

定义标签后可以使用分支(branch)或者测试(test)控制sed指令回到特定的标签位置

标签需要以冒号(:)开始,后面跟任意标签字符串(标签名称),冒号与标签字符串之间不能有空格,如果字符串最后有空格,则空格也被理解为标签名称的一部分

有了标签,就可以通过b或者t指令跳转至标签的位置,如果b或者t指令跳转的目标标签不存在,则sed直接跳转至命令结束位置。

区别是b为无条件跳转t为有条件跳转。t需要根据前面的s替换指令的结果决定是否跳转。

需注意的是,这里的跳转只影响sed指令的执行顺序,对输入的数据行没有影响

Branch无条件跳转

语法格式:

:label
sed 指令序列
... ...
b label
sed -n ':top;=;p;4b top' test.txt		#死循环

在这里插入图片描述
执行流程:
1、读取第1行,定义名称为top的标签,执行=和p指令,屏幕输出行号1和第一行的内容,因为第一行的行号不等于4,所以不会执行b跳转指令
2、接着读取2、3行
3、当读取第4行时,也是先执行=和p指令,但第4行与b前面的行号匹配,因此会执行b跳转指令,跳转到top,等于做了一次循环。回到top,继续执行=和p,因为跳转仅影响指令的执行顺序,不会导致数据行的跳转变化,所以当前行始终是第4行,导致死循环

# sed -n '/go/b label;=;:label;p' test.txt
1
1:hello the world.
2:go spurs go.
3
3:123 456 789.
4
4:hello the beijing.
5
5:I am Devops.

当遇到包含go字符串的行时,就跳过了=指令,直接跳到标签为label的位置,因此只有第2行没有单独打印行号

# sed '/beijing/b end;s/\./!/;:end' test.txt
1:hello the world!
2:go spurs go!
3:123 456 789!
4:hello the beijing.
5:I am Devops!

只有beijing所在行的.没有替换为!

# sed '/hello/{s/hello/nihao/;:next;n;b next}' test.txt
1:nihao the world.
2:go spurs go.
3:123 456 789.
4:hello the beijing.
5:I am Devops.

只替换出现的第一个hello为nihao

test有条件跳转

使用时必须与s替换操作配合使用。当s替换操作成功时则执行test跳转,如果目标标签不存在,则跳转至指令的结束位置,反之,s替换操作不成功,则不执行test跳转操作。

基本语法:

:label
sed指令序列
... ...
s/regex/replace/
t label

素材文件:
vi t1.txt

phone:
    13312345678
mail:test@test.com
phone:13611112222
mail:
    beat@beat.com
# sed -r ':start;/:$/N;s/\n +//;t start' t1.txt
phone:13312345678
mail:test@test.com
phone:13611112222
mail:beat@beat.com

先定义一个start标签,匹配以冒号结尾的行,匹配到后把下一行的内容追加到模式空间,这时模式空间有两行数据,再使用s将\n和后面的若干空格替换为空,最后test有条件跳转。

为MAC地址添加分隔符:

# echo fe45008a2592 | sed -r ':loop;s/([^:]+)([0-9a-f]{2})/\1:\2/;t loop'
fe:45:00:8a:25:92

四、技巧

只替换第一个匹配

sed匹配第N个进行替换
素材文件/tmp/t1.txt

456
abc
123
abc

替换第一个abc为789

sed -i "0,/abc/s//789/" /tmp/t1.txt

在这里插入图片描述

只删除第一个匹配行

sed -ri '0,/abc/{//d}' /tmp/t1.txt

截取字符串的最后一位

echo 112asf |sed 's/\(.*\)\(.\)$/\2/'		#结果为f

追加整行,并保留空格

sed  -i  "/要匹配的/a\  新的内容 "  filename		#a后面的反斜杠\表示它后面的都作为内容输出,包括空格 
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐