全网最详细的Linux命令系列-sed文本处理命令

时间:2022-02-17 01:43:10

Sed简介

SED是一个非交互式文本编辑器,它可对文本文件和标准输入进行编辑,标准输入可以来自键盘输入、文本重定向、字符串、变量,甚至来自于管道的文本,与VIM编辑器类似,它一次处理一行内容,Sed可以编辑一个或多个文件,简化对文件的反复操作、编写转换程序等。

Sed命令的原理:在处理文本时把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),紧接着用SED命令处理缓冲区中的内容,处理完成后把缓冲区的内容输出至屏幕或者写入文件。逐行处理直到文件末尾,然而如果打印在屏幕上,实质文件内容并没有改变,除非你使用重定向存储输出或者写入文件。

Sed语法参数

参数格式为:

sed    [-Options]     [‘Commands’]    filename;

sed工具默认处理文本,文本内容输出屏幕已经修改,但是文件内容其实没有修改,需要加-i参数即对文件彻底修改;

-e<script>或--expression=<script>   以选项中指定的script来处理输入的文本文件。
-f<script文件>或--file=<script文件> 以选项中指定的script文件来处理输入的文本文件。
-h 或--help 显示帮助。
-n或--quiet或--silent 仅显示script处理后的结果。
-r 使用扩展正则表达式
x #x为指定行号;
x,y #指定从x到y的行号范围;
/pattern/ #查询包含模式的行;
/pattern/,/pattern/ #查询包含两个模式的行;
/pattern/,x #从与pattern的匹配行到x号行之间的行;
x,/pattern/ #从x号行到与pattern的匹配行之间的行;
x,y! #查询不包括x和y行号的行;
r #从另一个文件中读文件;
w #将文本写入到一个文件;
y #变换字符;
q #第一个模式匹配完成后退出;
l #显示与八进制ASCII码等价的控制字符;
{} #在定位行执行的命令组;
p #打印匹配行;
= #打印文件行号;
a\ #在定位行号之后追加文本信息;
i\ #在定位行号之前插入文本信息;
d #删除定位行;
c\ #用新文本替换定位文本;
s #使用替换模式替换相应模式;
& #引用已匹配字符串
first~step #步长,每 step 行,从第 first 开始
$ #匹配最后一行
/regexp/ #正则表达式匹配行
number #只匹配指定行
addr1,addr2 #开始匹配 addr1 行开始,直接 addr2 行结束
addr1,+N #从 addr1 行开始,向后的 N 行
addr1,~N #从 addr1 行开始,到 N 行结束

Sed实例练习(模式空间)

示例文本:

[root@localhost ~]# cat test.txt
www.test.net
www.baidu.com
www.taobao.com
www.sina.com
old old old
new new new

替换test.txt文本中old为new:

sed    's/old/new/g'       test.txt

打印test.txt文本第一行至第三行:

sed    -n  '1,3p'           test.txt

打印test.txt文本中第一行与最后一行:

sed    -n '1p;$p'           test.txt

删除test.txt第一行至第三行、删除匹配行至最后一行:

sed       '1,3d'             test.txt
sed '/test/,$d' test.txt

删除test.txt最后6行及删除最后一行:

for   i  in `seq 1 6`;do  sed  -i   '$d'  test.txt ;done
sed '$d' test.txt

删除test.txt最后一行:

sed       '$d'             test.txt

在test.txt查找test所在行,并在其下一行添加word字符,a表示在其下一行添加字符串:

sed    '/test/aword'      test.txt

在test.txt查找test所在行,并在其上一行添加word字符,i表示在其上一行添加字符串:

sed    '/test/i word'       test.txt

在test.txt查找以test结尾的行尾添加字符串word,$表示结尾标识,&在Sed中表示添加:

sed   's/test$/& word/g'     test.txt

在test.txt查找www的行,在其行首添加字符串word,^表示起始标识,&在Sed中表示添加:

sed   '/www/s/^/& word/'    test.txt

多个sed命令组合,使用-e参数:

sed  -e  '/www.jd.com/s/^/&1./'  -e  's/www.jd.com$/&./g'  test.txt

多个sed命令组合,使用分号“;”分割:

sed  -e  '/www.jd.com/s/^/&1./;s/www.jd.com$/&./g'  test.txt

Sed读取系统变量,变量替换:

TEST=WWW.test.NET
Sed “s/www.jd.com/$TEST/g” test.txt

修改Selinux策略enforcing为disabled,查找/SELINUX/行,然后将其行enforcing值改成disabled、!s表示不包括SELINUX行:

sed  -i   '/SELINUX/s/enforcing/disabled/g' /etc/selinux/config
sed -i '/SELINUX/!s/enforcing/disabled/g' /etc/selinux/config

去除空格httpd.conf文件空行或者是#号开头的行

sed '/^#/d;/^$/d' /etc/httpd/conf/httpd.conf

打印是把匹配的打印出来,删除是把匹配的删除,删除只是不用-n 选项

IP 加单引号

echo '10.10.10.1 10.10.10.2 10.10.10.3' |sed -r 's/[^ ]+/"&"/g'
"10.10.10.1" "10.10.10.2" "10.10.10.3"

分组() 匹配

对 1-4 行的 html 进行替换

示例文件:

http://www.baidu.com/index.html
http://www.baidu.com/1.html
http://post.baidu.com/index.html
http://mp3.baidu.com/index.html
http://www.baidu.com/3.html
http://post.baidu.com/2.html
tail  /etc/services | sed '1,4 s/html/txt/'
http://www.baidu.com/index.txt
http://www.baidu.com/1.txt
http://post.baidu.com/index.txt
http://mp3.baidu.com/index.txt
http://www.baidu.com/3.html
http://post.baidu.com/2.html

分组使用,在第2列后面添加 test

tail /etc/services |sed -r 's/(.*) (.*)(#.*)/\1\2test \3/'
3gpp-cbsp 48049/tcp test # 3GPP Cell Broadcast Service
isnetserv 48128/tcp test # Image Systems Network Services
isnetserv 48128/udp test # Image Systems Network Services
blp5 48129/tcp test # Bloomberg locator
blp5 48129/udp test # Bloomberg locator
com-bardac-dw 48556/tcp test # com-bardac-dw
com-bardac-dw 48556/udp test # com-bardac-dw
iqobject 48619/tcp test # iqobject
iqobject 48619/udp test # iqobject 第一列是第一个小括号匹配,第二列第二个小括号匹配,第三列一样。将不变的字符串匹配分组,再通过\数字按分组顺序反向引用。

基本正则表达式中支持分组,而在扩展正则表达式中,分组的功能更加强大,也可以说才是真正的分组

():分组,后面可以使用\1 \2 \3...引用前面的括号分组。

处理以下文件内容,将域名取出并进行计数排序,如处理:

http://www.baidu.com/index.html
http://www.baidu.com/1.html
http://post.baidu.com/index.html
http://mp3.baidu.com/index.html
http://www.baidu.com/3.html
http://post.baidu.com/2.html

得到如下结果:

域名的出现的次数域名

[root@localhost shell]# cat file | sed -e ' s/http:\/\///' -e ' s/\/.*//' | sort | uniq -c | sort -rn
3 www.baidu.com
2 post.baidu.com
1 mp3.baidu.com
[root@codfei4 shell]# awk -F/ '{print $3}' file |sort -r|uniq -c|awk '{print $1"\t",$2}'
3 www.baidu.com
2 post.baidu.com
1 mp3.baidu.com*

将协议与端口号位置调换

tail /etc/services |sed -r 's/(.*)(\<[0-9]+\>)\/(tcp|udp)(.*)/\1\3\/\2\4/'
3gpp-cbsp tcp/48049 # 3GPP Cell Broadcast Service
isnetserv tcp/48128 # Image Systems Network Services
isnetserv udp/48128 # Image Systems Network Services
blp5 tcp/48129 # Bloomberg locator
blp5 udp/48129 # Bloomberg locator
com-bardac-dw tcp/48556 # com-bardac-dw
com-bardac-dw udp/48556 # com-bardac-dw
iqobject tcp/48619 # iqobject
iqobject udp/48619 # iqobject
matahari tcp/49000 # Matahari Broker

字符位置调换

替换 x 字符为大写:

# echo "abc cde xyz" |sed -r 's/(.*)x/\1X/'
abc cde Xyz

456 与 cde 调换:

# echo "abc:cde;123:456" |sed -r 's/([^:]+)(;.*:)([^:]+$)/\3\2\1/'
abc:456;123:cde

注释匹配行后的多少行

[root@localhost ~]# sed '/5/,+3s/^/@/' 1.txt
1
2
3
4
@5
@6
@7
@8
9
10

注释指定多行

[root@localhost ~]# sed -r 's/^3|^5|^7/#&/' 1.txt
1
2
#3
4
#5
6
#7
8
9
10

去除开头和结尾空格或制表符

echo " 1 2 3 " |sed 's/^[ \t]*//;s/[ \t]*$//'
1 2 3

Sed实例练习(保留空间)

Sed之所以能以行为单位进行修改文本或者编辑文本,主要原因是因为它使用了两个空间:一个是活动的“模式空间”,另一个是辅助作用的“保留空间”.

模式空间:可以想象成工程里面的流水线,所有的数据读取过来之后就直接在上面进行工作。

保留空间:可以想象成是仓库,我们在进行数据处理的时候,会把数据读取到保留空间中,作为数据的暂存区域,需要时再进行调出。

SED高级命令可以分为三种功能:

N、D、P:处理多行模式空间的问题;
H、h、G、g、x:将模式空间的内容放入保留空间以便接下来的编辑;
:、b、t:在脚本中实现分支与条件结构(标签)。
n #读取下一个输入行,用下一个命令处理新的行;
N #将当前读入行的下一行读取到当前的模式空间。
d #删除模式空间的所有内容,开始下一个循环
D #删除模式空间的第一行,开始下一个循环;
p #打印当前模式空间的所有内容;
P #打印模式空间的第一行,开始下一个循环
h #将模式缓冲区的文本复制到保持缓冲区;
H #将模式缓冲区的文本追加到保持缓冲区;
x #互换模式缓冲区和保持缓冲区的内容;
g #将保持缓冲区的内容复制到模式缓冲区;
G #将保持缓冲区的内容追加到模式缓冲区。

在test.txt每行后加入空行,也即每行占永两行空间,每一行后边插入一行空行、两行空行及前三行每行后插入空行:

sed     '/^$/d;G'            test.txt  #删除的是匹配的条件行,留下的是模式空间处理的行,G参数是将保持空间内的行追加到模式空间去(保持空间默认是空的)。
sed '/^$/d;G;G' test.txt #后面跟2个G 是将G保持空间内的两行追加到模式空间
sed '/^$/d;1,3G;' test.txt #1,3G 是只将追加3行到模式空间

将test.txt偶数行删除及隔两行删除一行:

sed    'n;d'              test.txt 		#n参数将输入行的下一行显示出来,而后面的d 正好会将显示的行进行删除,测试“sed –n ‘n;p’ file ”
sed 'n;n;d' test.txt, #2个n参数将输入的的下两行显示出来,正好使用d都删除,形成隔两行删一行的效果。

在test.txt匹配行前一行、后一行插入空行以及同时在匹配前后插入空行:

sed  '/test/{x;p;x;}'      test.txt     #x参数是让模式空间和保持空间互相交换,匹配test时,将模式空间内的数据换成了保持空间内的数据,保持空间默认是空的,所以前一行会变成空行,然后将空行打印出来
sed '/test/G' test.txt #G参数配置上之后将保持空间的内容追加到模式空间去。
sed '/test/{x;p;x;G;}' test.txt #综合以上两个参数配置。

在test.txt每行前加入顺序数字序号、加上制表符\t及.符号:

sed = test.txt| sed 'N;s/\n/ /'   # “=”等号打印当前行号码。N参数是配置将当前读入行的下一行读取到当前的模式空间,就是把下一行读到当前的模式空间来,利用s替换换行符,形成数字顺序效果。
sed = test.txt| sed 'N;s/\n/\t/' #案例同上
sed = test.txt| sed 'N;s/\n/\./' #案例同上

删除test.txt行前和行尾的任意空格:

sed 's/^[ \t]*//;s/[ \t]*$//' test.txt  #s替换 删除行前

打印test.txt关键词old与new之间的内容:

sed -n '/old/,/new/'p     test.txt   #-n参数可以打印匹配的条件内容行

打印及删除test.txt最后两行:

sed   '$!N;$!D'             test.txt
N 读取下一行并追加输入到模式空间
D 删除模式空间的第一行,开始下一个循环
#读取1,$!条件满足(不是尾行,#N前加$!表示末尾行不执行N),执行N命令,读取下一行并追加输入到模式空间,得出1\n2。执行$!D,不是最后一行,所以执行D,删除模式空间的第一行,开始下一个循环,模式空间由1\n2成了2。读取第二行,执行 N 命令,此时模式空间是 3\n4,执行 D 命令删除模式空间第一行 3,剩余4,直到执行N读入第5行,$!条件不满足(不是尾行),不执行N命令:继续读入6行,这里模式空间为:5\n6,$!D,因为是最后一行,所以不执行D,控制流到达脚本底部,输出模式空间的内容
sed   'N;$!P;$!D;$d'      test.txt
P 打印模式空间的第一行
N 读取下一行并追加输入到模式空间
D 删除模式空间的第一行,开始下一个循环
d 删除匹配的行
#读取第一行,执行N,此时得出1\n2,P打印从开始到第一个\n的内容,执行$!D,不是最后一行,所以执行D,删除模式空间的第一行,开始下一个循环,模式空间由1\n2成了2,$d 是因为不是末行所以不执行,读取第二行,执行 N 命令,此时模式空间是 3\n4,执行 P 命令显示模式空间第一行 3,执行D,删除模式空间的第一行,剩余4,读取第5行,执行N,将第6行进行读取,执行$!P,因为是最后一行不执行P,执行$!D,因为是最后一行,不执行D,最后执行$d,删除模式空间5/6行。

合并上下两行,也即两行合并:

sed    '$!N;s/\n/ /'          test.txt  #N前加$!表示末尾行不执行N
sed 'N;s/\n/ /' test.txt

Sed中标签使用(: 、b 和 和 t )

标签可以控制流,实现分支判断。

:       	lable name 定义标签
b lable 跳转到指定标签,如果没有标签则到脚本末尾
t lable 跳转到指定标签,前提是 s///命令执行成功

将换行符替换成逗号

方法 1:

seq 6 |sed 'N;s/\n/,/'
1,2
3,4
5,6

这种方式并不能满足我们的需求,每次 sed 读取到模式空间再打印是新行,替换\n 也只能对 N 命令,追加后的 1\n2 这样替换。

这时就可以用到标签了:

seq 6 |sed ':a;N;s/\n/,/;b a'
1,2,3,4,5,6
看看这里的标签使用,:a 是定义的标签名,b a 是跳转到 a 位置。
sed 读取第一行 1,N 命令读取下一行 2,此时模式空间是 1\n2$,执行替换,此时模式空间是1,2$,执行 b 命令再跳转到标签 a 位置继续执行 N 命令,读取下一行 3 追加到模式空间,此时模式空间是 1,2\n3$,再替换,以此类推,不断追加替换,直到最后一行 N 读不到下一行内容退出。

方法 2:

seq 6 |sed ':a;N;$!b a;s/\n/,/g'
1,2,3,4,5,6
先将每行读入到模式空间,最后再执行全局替换。$!是如果是最后一行,则不执行 b a 跳转,最后执行全局替换。
seq 6 |sed ':a;N;b a;s/\n/,/g'
1
2
3
4
5
6
可以看到,不加$!是没有替换,因为循环到 N 命令没有读到行就退出了,后面的替换也就没执行。

每三个数字加个一个逗号

# echo "123456789" |sed -r 's/([0-9]+)([0-9]+{3})/\1,\2/'

123456,789

# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{3})/\1,\2/;t a'

123,456,789

# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{2})/\1,\2/;t a'

1,23,45,67,89
执行第一次时,替换最后一个,跳转后,再对 123456 匹配替换,直到匹配替换不成功,不执行 t 命令。

忽略大小写匹配(I )

# echo -e "a\nA\nb\nc" |sed 's/a/1/Ig'

1
1
b
c

获取总行数(# )

seq 10 |sed -n '$='

Sed 脚本使用编写方法

<1>从文件读入命令

sed -f sed.sh
sed.sh文件内容:
s/root/yerik/p
s/bash/csh/p

<2>直接运行脚本 ./sed.sh /etc/passwd

#!/bib/sed -f
s/root/yerik/p
s/bash/csh/p

Sed 扩展练习高级替换

1,删除文件每行的第一个字符。

sed -n 's/^.//gp' /etc/passwd
sed -nr 's/(.)(.*)/\2/p' /etc/passwd
sed -r 's/^.//g' test.txt

2,删除文件每行的第二个字符。

sed -nr 's/(.)(.)(.*)/\1\3/p' /etc/passwd
sed -r 's/(^.)(.)/\1/g' test.txt

3,删除文件每行的最后一个字符。

sed -nr 's/.$//p' /etc/passwd
sed -nr 's/(.*)(.)/\1/p' /etc/passwd
sed -r 's/(.)$//g' test.txt

4,删除文件每行的倒数第二个字符。

sed -nr 's/(.*)(.)(.)/\1\3/p' /etc/passwd
sed -r 's/(.)(.)$/\2/g' test.txt

5,删除文件每行的第二个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\1\2\3\5/p' /etc/passwd
sed -r 's/([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)(.*)/\1\2\4\5/' test.txt

6,删除文件每行的倒数第二个单词。

sed -nr 's/(.*)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]*)/\1\2\4\5\6/p' /etc/passwd
sed -r 's/([a-Z]+)([^a-Z])([a-Z]+)$/\2\3/g' test.txt

7,删除文件每行的最后一个单词。

sed -nr 's/(.*)([^a-Z]+)([a-Z]+)([^a-Z]*)/\1\2\4/p' /etc/passwd
sed -r 's/([a-Z]+)$//g' test.txt
sed -r 's/(.*)([^a-Z]+)([a-Z]+)/\1\2/' test.txt

8,交换每行的第一个字符和第二个字符。

sed -nr 's/(.)(.)(.*)/\2\1\3/p' /etc/passwd
sed -r 's/(.)(.)/\2\1/' test.txt

9,交换每行的第一个字符和第二个单词

sed -r 's/(^.)([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)/\5\2\3\4\1/' test.txt

9,交换每行的第一个单词和第二个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\1\4\3\2\5/p' /etc/passwd

10,交换每行的第一个单词和最后一个单词。

sed -r 's/([a-Z]+)([^a-Z]+)(.*)([^a-Z]+)([a-Z]+)/\5\2\3\4\1/' test.txt

11,删除一个文件中所有的数字。

sed 's/[0-9]*//g' test.txt

12,删除每行开头的所有空格。

sed -n 's/^\ *//p' /etc/passwd
sed -nr 's/( *)(.*)/\2/p' test.txt
sed 's/^ *//' test.txt

13,用制表符替换文件中出现的所有空格。

sed -n 's/\ /\t/gp' test.txt
sed -r 's/( +)/\t/g' test.txt
sed 's/ /\t/g' test.txt

14,把所有大写字母用括号()括起来。

sed -nr 's/([A-Z])/(&)/gp' test.txt
sed -n 's/[A-Z]/(&)/gp' test.txt

15,打印每行3次。

sed 'p;p' test.txt
sed -n 'p;p;p' test.txt

16,隔行删除。

sed -n '1~2p' test.txt
sed '1d;n;d' ww.txt
sed '1~2d' ww.txt
sed '0~2d' ww.txt

17,把文件从第22行到第33行复制到第44行后面。

sed '1,21h;22h;23,33H;44G' pass
cat -n /etc/passwd | sed '22h;23,33H;44G'

18,把文件从第22行到第33行移动到第44行后面。

sed '22{h;d};23,33{H;d};44G' pass
cat -n /etc/passwd | sed '22{h;d};23,33{H;d};44G'

19,只显示每行的第一个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)(.*)/\2/p' /etc/passwd
sed -r 's/([a-Z]+)(.*)/\1/' test.txt

20,打印每行的第一个单词和第三个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\2--\4/p' /etc/passwd
sed -r 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\2\6/' test.txt

21,将格式为 mm/yy/dd 的日期格式换成 mm;yy;dd

date +%m/%Y/%d |sed -n 's#/#;#gp'

22, 逆向输出

cat a.txt
ABC
DEF
XYZ
sed '1!G;h;$!d' a.txt
输出样式变成
XYZ
DEF
ABC

Sed 作业练习:

  1. 把/etc/passwd 复制到/root/test.txt,用sed打印所有行
  2. 打印test.txt的3到10行
  3. 打印test.txt 中包含 ‘root’ 的行
  4. 删除test.txt 的15行以及以后所有行
  5. 删除test.txt中包含 ‘bash’ 的行
  6. 替换test.txt 中 ‘root’ 为 ‘toor’
  7. 替换test.txt中 ‘/sbin/nologin’ 为 ‘/bin/login’
  8. 删除test.txt中5到10行中所有的数字
  9. 删除test.txt 中所有特殊字符(除了数字以及大小写字母)
  10. 把test.txt中第一个单词和最后一个单词调换位置
  11. 把test.txt中出现的第一个数字和最后一个单词替换位置
  12. 把test.txt 中第一个数字移动到行末尾
  13. 在test.txt 20行到末行最前面加 ‘aaa:’

sed习题答案

1. /bin/cp /etc/passwd  /root/test.txt ;  sed -n '1,$'p test.txt
2. sed -n '3,10'p test.txt
3. sed -n '/root/'p test.txt
4. sed '15,$'d test.txt
5. sed '/bash/'d test.txt
6. sed 's/root/toor/g' test.txt
7. sed 's#sbin/nologin#bin/login#g' test.txt
8. sed '5,10s/[0-9]//g' test.txt
9. sed 's/[^0-9a-zA-Z]//g' test.txt
10. sed 's/\(^[a-Z]*\)\([^a-Z].*\)\([^a-Z]\)\([a-Z]*$\)/\4\2\3\1/' test.txt
11. sed 's#\([^0-9][^0-9]*\)\([0-9][0-9]*\)\([^0-9].*\)\([^a-zA-Z]\)\([a-zA-Z][a-zA-Z]*$\)#\1\5\3\4\2#' test.txt
12. sed 's#\([^0-9][^0-9]*\)\([0-9][0-9]*\)\([^0-9].*$\)#\1\3\2#' test.txt
13. sed '20,$s/^.*$/aaa:&/' test.txt