在做数据开发中,经常需要通过shell脚本/命令来针对文本进行预处理,sed是一个很强大的流式处理命令,笔者几乎每天都会用到,在这统一梳理总结了下! 其实 sed 很简单,比vim简单很多了!
1. 基础
核心概念
- 两个空间: 模式空间(pattern space); 交换空间(hold space 保持空间)
- 模式空间:容纳当前行的缓冲区,即通过
模式匹配到的行
被读入该空间中 - 保持空间:一个辅助缓冲区,可以和模式空间进行交互(通过h,H,g,G),但命令
不能直接作用于该空间
,在进行数据处理时作为“暂存区域”
- 模式空间:容纳当前行的缓冲区,即通过
- 执行步骤: 1)读入一行数据到模式空间 2)在模式空间执行sed命令 3)将更新/修改后的内容输出 4)清空模式空间,并重复第一步,直到文件结束 执行流程
联想记忆: 模式对应G(在左边), 交换对应H(在右边)
- 两种执行方式:
- 一般常用:
sed [options] 'command' file(s)
- 脚本文件:
sed [options] -f scriptfile file(s)
- 一般常用:
- 查看帮助:
man sed
- 官方查询文档:TLDP--The Linux Document Project
参数
- sed最后会输出
模式空间
的所有内容(除非指定了-n
参数) - 变量传递通过
-v
参数(建议) -e
: 支持多个编辑命令- 命令的执行顺序对结果有影响
- (-e)选项允许在同一行里执行多条命令。如例子所示,第一条命令删除1至5行,第二条命令用check替换test。命令的执行顺序对结果有影响。如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果。
sed -e '1,5d' -e 's/test/check/' example
- 一个比-e更好的命令是`--expression`。它能给sed表达式赋值。
`sed --expression='s/test/check/' --expression='/love/d' example`
正则匹配(元字符集)
代码语言:shell复制^
#锚定行的开始 如:/^sed/匹配所有以sed开头的行。
$
#锚定行的结束 如:/sed$/匹配所有以sed结尾的行。
.
#匹配一个非换行符的字符 如:/s.d/匹配s后接一个任意字符,然后是d。
*
#匹配零或多个字符 如:/*sed/匹配所有模板是一个或多个空格后紧跟sed的行。
[]
#匹配一个指定范围内的字符,如/[Ss]ed/匹配sed和Sed。
[^]
#匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。
(..)
#保存匹配的字符,如s/(love)able/1rs,loveable被替换成lovers。
&
#保存搜索字符用来替换其他字符,如s/love/**&**/,love这成**love**。
<
#锚定单词的开始,如:/<love/匹配包含以love开头的单词的行。
>
#锚定单词的结束,如/love>/匹配包含以love结尾的单词的行。
x{m}
#重复字符x,m次,如:/0{5}/匹配包含5个o的行。
x{m,}
#重复字符x,至少m次,如:/o{5,}/匹配至少有5个o的行。
x{m,n}
#重复字符x,至少m次,不多于n次,如:/o{5,10}/匹配5--10个o的行。
2. 常用命令
s 替换文本(匹配)
代码语言:shell复制#在整行范围内把test替换为mytest。如果没有g标记,则只有每行第一个匹配的test被替换成mytest。
$ sed 's/test/mytest/g' example
#s选项和p标志一起使用表示只打印那些发生替换的行。也就是说,如果某一行开头的test被替换成mytest,就打印它。
$ sed -n 's/^test/mytest/p' example
#&符号表示替换换字符串中被找到的部份。所有以192.168.0.1开头的行都会被替换成它自已加 localhost,变成192.168.0.1localhost。
$ sed 's/^192.168.0.1/&localhost/' example
#love被标记为1,所有loveable会被替换成lovers,而且替换的行会被打印出来。
$ sed -n 's/(love)able/1rs/p' example
#不论什么字符,紧跟着s命令的都被认为是新的分隔符,所以,“#”在这里是分隔符,代替了默认的“/”分隔符。表示把所有10替换成100。
$ sed 's#10#100#g' examplex
#选定行的范围:逗号
#所有在模板test和check所确定的范围内的行都被打印。
$ sed -n '/test/,/check/p' example
#打印从第五行开始到第一个包含以test开始的行之间的所有行。
$ sed -n '5,/^test/p' example
#对于模板test和west之间的行,每行的末尾用字符串sed test替换。
$ sed '/test/,/check/s/$/sed test/' example
#如果test被匹配,则移动到匹配行的下一行,替换这一行的aa,变为bb,并打印该行,然后继续。
$ sed '/test/{ n; s/aa/bb/; }' example
# 替换匹配到的文本
sed -i "s|^(.*)#(SSLCertificateChainFile).*|12 /etc/apache2/ssl/1_root_bundle.crt|g" default-ssl.conf
# 替换匹配到的文本
sed -i "s download.docker.com ${DOCKER_RE_REPO} " /etc/yum.repos.d/docker-ce.repo
# 匹配再替换
sed -i "/^baseurl=http:/ { s,http://.*/hdp,http://${YUM_SERVER_IP}/hdp, } " hdp-utils.repo
# 匹配
# 如果test被匹配,则移动到匹配行的下一行,替换这一行的aa,变为bb,并打印该行,然后继续。
sed '/test/{ n; s/aa/bb/; }' example
# 和;都得转义
# 匹配slash无法用其他字符替换
# a:在匹配行的下一行追加, 可以不加,但加上表示后面为追加内容,且表示空格也能插入
sed -i "/sinclude /etc/nginx/conf.d/*.conf;/a include /deploy/nginx/*.conf;" /etc/nginx/nginx.conf.bak
a,c,i,q,r,=,#,y
a
: 在当前行后面加入一行文本i
: 在当前行上面插入文本c
: 用新的文本改变本行的文本q
: 退出Sedr
: 从file中读行w
: 表示把行写入一个文件 (W: 追加)=
: 打印当前行号码#
: 把注释扩展到下一个换行符以前。y
: 一个字符翻译为另外的字符(但是不用于正则表达式)
- 用法举例
# 从文件读入:r命令
# file里的内容被读进来,显示在与test匹配的行后面,如果匹配多行,则file的内容将显示在所有匹配行的下面。 sed '/test/r file' example
# 写入文件:w命令
#在example中所有包含test的行都被写入file里。
sed -n '/test/w file' example
# 追加命令:a命令
# this is a example'被追加到以test开头的行后面,sed要求命令a后面有一个反斜杠。
sed '/^test/a\--->this is a example' example
# 插入:i命令
# 如果test被匹配,则把反斜杠后面的文本插入到匹配行的前面。
sed '/test/i\
new line
-------------------------' example
#变形:y命令
#把1--10行内所有abcde转变为大写,注意,正则表达式元字符不能使用这个命令。
sed '1,10y/abcde/ABCDE/' example
#退出:q命令
#打印完第10行后,退出sed
sed '10q' example 。
n,N
模式空间
n
: 下一行 ,模式空间内容被覆盖N
: 将当前行和下一行(中间n保留),一起追加到模式空间
- N: 追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。
[root@VM_0_6_centos ~]# seq 6 | sed -n '1{n;p}'
2
seq 6 | sed -n '1{N;p}'
1
2
[root@VM_0_6_centos ~]# seq 6 | sed -n '2{N;p}'
2
3
[root@VM_0_6_centos ~]# seq 6 | sed -n '2{n;p}'
3
h,H,p,P,x
交换空间
h
: 用模式空间内容覆盖交换空间H
: 将模式空间内容追加到交换空间p/P
: 模式空间输出x
: 交换空间&模式空间内容交换g
: 将交换空间的内容,覆盖到模式空间G
: 将交换空间的内容,追加到模式空间
- 互换模式空间和保持缓冲区的内容。也就是把包含test与check的行互换
sed -e '/test/h' -e '/check/x' example
- 例子
[root@VM_0_6_centos ~]# seq 6 | sed '1h;3g'
1
2
1
4
5
6
[root@VM_0_6_centos ~]# seq 6 | sed '1h;3G'
1
2
3
1
4
5
6
- 复杂的例子:
sed -n '/CST/h;/syncing/{x;//!p;g;P}
//!
:简写,拿到最近的匹配结果,!
表示若匹配不到
D,d
模式空间,删除 模式空间没有回车符,D/d一样 D/d执行后,都会跳到下一行(不管模式空间是否有内容)
- 简单用法
#删除第N行
sed -i 'Nd' filename
#删除第N~M行
sed -i 'N,Md' filename # file的[N,M]行都被删除
#删除shell变量表示的行号(配合for等语句使用)
sed -i "${var1},${var2}d" filename # 这里引号必须为双引号
#删除最后一行
sed -i '$d' filename
- 打印带有hello段落(段和段之间用空隔分开)
- d后面的操作不执行(直接跳到下一行)
- 比较复杂,直接忽略:
sed '/./{H;d;x;/hello/!d
- awk实现:打印带有hello的段落
awk -v RS=' ' "/hello/
cat >test.txt <<EOF
1111
2222
3333hello
4444
5555
6666
EOF
awk -v RS=' ' "/hello/" test.txt
3333hello
4444
- 删除文件最后五行内容
- 维持一个队列: 2-5行循环执行a,即N操作
- 第6行的时候,打印模式空间中的第1行(P),并删除第1行(D)
- 最后一行的时候,把模式空间清空(
$d
) seq 7 | sed ':a;$d;N;2,5ba;P;D'
- 波浪线用法:
a~b
:a起始点,b间隔 - 每隔两行输出一行
seq 10 | sed -n '1~3p'
1~3
: 1-从什么时候开始 ,3表示间隔3行
seq 10 | sed -n '0~3p'
seq 10 | awk 'NR%3==1'
1
4
7
10
addr1,addr2用法
依次匹配
addr1, addr2
,匹配到即进行操作 addr1:匹配到后执行(开关打开),如果当前行>addr1
addr2:匹配到后执行(开关关闭)
- 准确的理解:
seq 5 | sed '1,2d;1,2d'
- 输出的应该是第4和第5行,因为第二行删除后,第一个1,2d关闭
- 再读入第3行时,匹配到第2个1,2d,
因为3>1
,所以匹配成功,执行d删除第3行 - 继续读入第4行后,
发现2<4
,匹配不成功,关闭,所以第4行不做d操作 - 接着第5行后所有内容因为
命令全部关闭
,直接输出
cat >test.txt <<EOF
a1
a2
a3
EOF
# 输出1-3行
sed '1,3{s/a/ax/}' test.txt
ax1
ax2
ax3
# 第一行就匹配到了a
sed '0,/a/{s/a/ax/}' test.txt
ax1
a2
a3
# 第一行匹配到1, 第二行匹配到a
sed '1,/a/{s/a/ax/}' test.txt
ax1
ax2
a3
# 一直匹配不到x
sed '1,/x/{s/a/ax/}' test.txt
ax1
ax2
ax3
t,b标签:
多用于做循环 t: 没有匹配到时跳转到label,无label则跳转到结尾
b: 调准到特定的label
:a
: 定义label a- 把第偶数出现的1,改为0
cat >test.txt <<EOF
1
2
1
3
1
1
EOF
sed '/1/{:a;n;s/1/0/;tb;ba;:b}' test.txt
1
2
0
3
1
0
3. 简单自测
- G,H作用,以及和g,h区别
- G,H: 追加,G (交换空间->模式空间) H(模式空间->交换空间)
- g,h: 覆盖
- x作用
- 模式空间和交换空间交换
echo abc | sed 's/.*/&n&/'
&
即引用前面匹配的结果
- sed每隔10行输出一个
seq 5 | sed -n 'x;p'
seq 5 | sed -n 'x;p;x'
sed '$!N;$!D'
,sed 'N;D'
,sed '$!D;D'
4. 组合应用
- find, sed:同时替换多个文件内的服务名
find . -type f -print0 | xargs -0 sed -i "s/<SERVER_NAME>/${SVR_NAME}/g"