我的CSDN博客地址,有目录,更好观看
https://blog.csdn.net/u013332124/article/details/83016838
一、概述
什么是sed
SED的英文全称是 Stream EDitor,是linux上一款非常好用的非交互式的文本编辑器。sed可以做这些事:
- 文本替换
- 选择性的输出文本文件
- 从文本文件的某处开始编辑
- 无交互式的对文本文件进行编辑等
sed的工作原理
SED的工作原理就是分3个步骤:读取、执行、显示。 SED会从输入流读取每一行,然后根据命令执行一些操作后,把修改后的内容发送给输出流(也就是输出到控制台)。
要注意的是,sed不会修改原来文本的内容,只会将修改后的内容输出到控制台。
二、使用姿势
1. 主要语法
sed [参数] '[address][command]' file
sed支持的参数
这些选项是GNU规范定义的,可能对于某些版本的SED并不支持。其中前3个参数是所有sed版本都支持的
- -n, –quiet, –slient:默认情况下,模式空间中的内容在处理完成后将会打印到标准输出,该选项用于阻止该行为【所有的sed版本都支持】
- -e script,–expression=script:指定要执行的命令,使用该参数,我们可以指定多个命令【所有的sed版本都支持】
- -f script-file, –file=script-file:指定包含要执行的命令的脚本文件【所有的sed版本都支持】
- –follow-symlinks:如果提供该选项的话,在编辑的文件是符号链接时,SED将会跟随链接
- -i[SUFFIX],–in-place[=SUFFIX]:该选项用于对当前文件进行编辑,如果提供了SUFFIX的话,将会备份原始文件,否则将会覆盖原始文件。mac上的i参数后面要跟一个字符串,表示备份文件名,可以传空,不传的话会报错。
- -l N, –line-lenght=N:该选项用于设置行的长度为N个字符
- –posix:该选项禁用所有的GNU扩展
- -r,–regexp-extended:该选项将启用扩展的正则表达式
- -u, –unbuffered:指定该选项的时候,SED将会从输入文件中加载最少的数据,并且更加频繁的刷出到输出缓冲区。在编辑tail -f命令的输出,你不希望等待输出的时候该选项是非常有用的。
- -z,–null-data:默认情况下,SED对每一行使用换行符分割,如果提供了该选项的话,它将使用NULL字符分割行
address—行寻址
行寻址是sed使用中一个很重要的概念。行寻址主要是告诉sed后面的command命令要用于处理哪些行。如果没有指定的话则表示command命令将会被运用处理输入流的每一行。我们不仅可以通过行号来定位行,也可以通过匹配内容来定位行。如果不填的话默认就是匹配所有的行
# 没有行寻址,则输出test.txt 文本中的所有内容
# 由于在默认情况下,sed会输出输入流中的每一行,所以加上-n 禁止默认的输出
sed -n 'p' test.txt
# 输出test.txt文本中的第一行
# 执行体中的 1 表示第一行,p表示一个执行命令,就是打印这一行
sed -n '1p' test.txt
# 输出test.txt文本中的第1行到第3行的内容
sed -n '1,3p' test.txt
# 输出test.txt文本中的第3行到最后一行之间的内容,$ 表示最后一行
sed -n '3,$p' test.txt
# 输出test.txt文本中 以"hello"的行 到最后一行 之间的内容
# 这里的内容匹配可以接受正则表达式
sed -n '/^hello/,$p' test.txt
# 输出test.txt文本中 以"hello"的行 到 包含world的行 之间的内容
sed -n '/^hello/,/world/p' test.txt
command—sed支持的命令
sed支持的命令非常丰富,几乎可以满足大部分的使用场景。现在这个表的命令看不懂没关系,下面会一个个的详细介绍这些命令的使用场景和使用姿势。
命令 | 功能 |
---|---|
a | 在当前行后添加一行或多行。多行时除最后一行外,每行末尾需用“\”续行 |
c | 用新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用""续行 |
i | 在当前行之前添加一行或多行。多行时除最后一行外,每行末尾需用""续行 |
d | 删除行 |
D | 删除多行中的第一行 |
p | 打印行 |
p | 打印多行中的第一行 |
n | 读入下一输入行,并从下一条命令而不是第一条命令开始对其的处理 |
N | 提前读入下一行 |
q | 结束或退出sed |
r | 从文件中读取输入行 |
! | 对所选行以外的所有行应用命令 |
s | 用一个字符串替换另一个 |
g | 在行内进行全局替换 |
w | 将所选的行写入文件 |
l | 输出时带隐藏字符 |
y | 将字符替换为另一字符,对单个字符进行转换(不能对正则表达式使用y命令) |
& | 输出正则匹配到的内容 |
= | 输出行号 |
h | 把模式空间里的内容复制到暂存空间 |
H | 把模式空间里的内容追加到暂存空间 |
g | 把暂存空间里的内容复制到模式空间,覆盖原有的内容 |
G | 把暂存空间的内容追加到模式空间里,追加在原有内容的后面 |
x | 交换暂存缓冲区与模式空间的内容 |
-e 参数
# 先将文本的hello 转换为world。 之后在将第三行的内容删掉
sed -e 's/hello/world/' -e '3d' test.txt
# 效果等同于
sed -e 's/hello/world/;3d' test.txt
2. 各个命令的使用姿势
使用前往文本 test.txt输出以下内容
1. name,age,sex
2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
p — 打印行
sed 'p' test.txt
使用上面的语句进行输出的话,我们会发现每一行都输出了两次,这是由于sed默认会输出所有的行。之后匹配每一行时,又执行了p命令,等于又输出了一次。我们可以用'-n' 参数来屏蔽sed的默认输出行为。
sed -n 'p' test.txt
这样,文本就只会输出一次了。
P — 输出多行中的第一行
# 先用N会一下子加载两行,再用P命令输出多行中的第一行
# N 命令的解释看下面的内容
less test.txt| sed -n 'N;P'
# 输出
1. name,age,sex
3. today is 10.10
5. public class aaa
p命令会直接将模式空间内的内容全部输出来,有多少行输多少行(大多数情况下都是一行)。如果遇到模式空间有多行内容,大写P命令只会输出多行中的第一行。
关于模式空间的介绍请看下一节内容。
d — 删除行
# 删除第一行
sed '1d' test.txt
# 删除以today开头的那一行
sed '/^today/d' test.txt
# 删除以today开头的那一行到最后一行之间的所有内容
sed '/^today/,$d' test.txt
d的命令使用起来也比较简单。要注意的是,被删除的行默认是不会输出了,因此默认只会输出那些没被删除的行。
D — 删除多行中的第一行
less test.txt| sed 'N;D'
# 输出
6. ok
D命令比较奇怪。如果模式空间有多行内容的话,它会删除第一行的内容。关键的是,它删除之后还会继续执行命令。
以上面的命令为例子,第一次执行命令时加载第一行,遇上命令N后提前加载第二行,这时候模式空间有两行内容,遇到命令D后第一行的内容被删掉,之后继续执行命令,遇到N,加载第三行,遇到D,删除第二行…这样循环到最后一行的时候,就剩下第6行了。
另外,上面的命令mac上的sed版本不会输出任何内容。
关于模式空间的介绍请看下一节内容。
s — 替换行中的内容
替换命令的使用场景比较多,它的语法如下
[address1[,address2]]s/pattern/replacement/[flags]
pattern表示要匹配的内容,replacement表示要替换的内容,flags表示匹配的一些参数。匹配的内容和替换的内容之间用正斜杠'/'分隔开来,但是我们也可以用其他符号类代替,比如我们要用'#'来分割。
[address1[,address2]]s#pattern#replacement#[flags]
示例:
# 将第一行中的 ',' 替换成 '|' 并输出
sed -n '1s/,/|/p' test.txt
# 输出内容
1. name|age,sex
上面的我们发现只替换了一个匹配项,如果想把这一行的所有逗号都替换掉,就需要flags参数了。
# 将第一行中的 ',' 全部都替换成 '|' 并输出
sed -n '1s/,/|/gp' test.txt
# 输出内容
1. name|age|sex
最后的g表示全局替换。
其他的flags介绍
# n —> 只替换第2个逗号,可以换成任意数字n,表示只替换第n个匹配项
sed -n '1s/,/|/2p' test.txt
# w —> 将将替换后的行写入aaa.txt
sed -n '1s#,#|#pw aaa.txt' test.txt
# i -> 匹配的时候忽略大小写.有一些sed版本不支持,比如mac上面的sed就不支持
sed -n '1s/,/|/gip' test.txt
匹配字符串
# 注意,括号前一定要加上转义符号\
# 有些版本的sed不支持\+表示匹配1到n个的表达式,另外\w和\d也不支持,比如本人测试时mac就不支持
echo "Three One Two" | sed 's#\(\w\+\) \(\w\+\) \(\w\+\)#\2 \3 \1#'
# 输出
One Two Three
# \d 可以改用 [0-9] 来表示。后面会详细讲sed支持的一些通用正则表达式
# 下面这个语句可以在mac中的sed使用
echo "3,2,1" | sed 's#\([0-9]\),\([0-9]\),\([0-9]\)#\2,\3,\1#'
# 输出
2,3,1
在replacement块中,我们可以用 \n 表示匹配到的第n个匹配内容,然后使用,比如\2就表示第二个匹配到的匹配项。
a — 在行后添加内容
sed '1 a hello world+++++' test.txt
# 输出
1. name,age,sex
hello world+++++
2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
上面的语句在第一行之后插入一行新的内容'hello world+++++'。另外,上面的命令在mac版的sed下也无法执行,会报错。
# 在第三行到最后一行后面都插入语句
sed '3,$ a hello world+++++' test.txt
# 在匹配到class的行到最后一行的后面都插入语句
sed '/class/,$ a hello world+++++' test.txt
i — 在行前添加内容
sed '1 i hello world+++++' test.txt
# 输出
hello world+++++
1. name,age,sex
2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
用法和a命令相似
c — 把整行替换成指定的内容
# 将第一行替换成 hello world+++++
sed '1 c hello world+++++' test.txt
# 输出
hello world+++++
2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
# 将多行替换成单行
sed '1,3 c hello world+++++' test.txt
y — 对单个字符进行替换
# 将第1,2行中的所有字符替换成目标字符
# 比如扫描到h字符的时候,会被转换成1.扫描到e的时候,会被替换成字符2,以此类推
sed '1,2 y/hello/12345/' test.txt
# 输出
1. nam2,ag2,s2x
2. 12335 w5r3d
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
注意,y命令两边的字符数量必须完全一样,否则会报错。不要把它当做是单词的转换,这是一个一个
n — 对当前行的下一行进行操作
# 将匹配到name那行的下一行中的hello替换成world
sed '/name/n;s/hello/world/' test.txt
n 命令表示对目标行的下一行进行操作,而不是对当前行进行操作。
N — 提前加载下一行
# 提前加载下一行的内容并将两行之间的换行符替换成,号
less test.txt| sed 'N; s/\n/,/'
# 输出
1. name,age,sex,2. hello world
3. today is 10.10,4. my name is jack
5. public class aaa,6. ok
注意,该命令和n不同,n命令是加载下一行覆盖模式空间当前行的内容。而大写N命令是提前加载下一行的内容,然后追加到当前模式空间。并且到时下一行的内容不会再加载。上面命令的效果也就是1,2行一起处理,3,4行一起处理...
关于模式空间的介绍请看下一节内容。
r — 将外部文件读进来
echo 12345 > hello.txt
# 在第2行后面写入hello.txt的值
sed '2 r hello.txt' test.txt
r 命令用来读入外部文件的内容,然后在sed处理行的时候,在行之后插入该内容
w — 将命令处理的内容写入新的文件
# 将第2行的 hello 替换成 world后 写入hello.txt 文件中
sed -n '2s/hello/wolld/w hello.txt' test.txt
! — 对所选行以外的所有行应用命令
# 输出第二行外的所有行
sed -n '2! p' test.txt
# 输出 匹配到hello的行到匹配到class的行 以外 所有行
sed -n '/hello/,/class/! p' test.txt
l — 输出时带隐藏字符
# 输出1到3行,并将隐藏字符显示出来
sed -n '1,3l' test.txt
# 输出
1. name,age,sex$
2. hello world$
3. today is 10.10$
l 可以将隐藏的字符输出出来。比如当你不知道是空格还是制表符时,可以用l来输出。如果是制表符,则会输出\t,如果是换行符,则会输出$来。
q — 退出当前的执行流
# 执行行匹配到hello时就退出,不再往下执行
sed '/hello/ q' test.txt
当执行遇到q命令时,sed会关闭输入流,不再读取后面的行。
& — 输出正则匹配到的内容
& 命令用于输出匹配到的内容,一般和s修改命令一起使用。
# 去匹配 h.*lo,匹配到后用 '匹配项,yes' 来代替原有的内容
# 这里的匹配默认是用最长匹配,比如如果用的表达式是 h.* 。则会匹配h字符到该行的最后一个字符
less test.txt| sed -n 's/h.*lo/&,yes/p'
# 输出,hello 被替换成了 hello,yes
2. hello,yes world
= — 输出行号
= 命令用于在sed执行过程中输出行号
# 输出1-3行的行号
less test.txt | sed '1,3='
# 输出
1
1. name,age,sex
2
2. hello world
3
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
# 获取文件的总行数
less test.txt| sed -n '$ ='
3. 模式空间和暂存空间
上面一节已经将大部分常用命令介绍完了。还剩下h、H、g、G、x命令没有介绍。在介绍这5个命令前大家需要对sed的模式空间和暂存空间有一定了解。
这两个空间其实就是两个内存缓冲区,sed会将读取的每一行先放到模式空间中,然后再执行相应命令。注意,模式空间并不会保存之前读过的所有行记录。也就是说,当sed读取第二行的记录时,第一行的记录就已经不在模式空间中了。采用的是覆盖的方式
模式空间的内容是不断更新的,但是存在暂存空间里的行内容却会在一直存在(在sed命令执行过程中)。我们可以通过h命令将模式空间的当前行内容复制到暂存空间(覆盖),或者用H命令将模式空间的内容以追加的方式写到暂存空间的末尾。g和G命令就是反着来的,g命令是将暂存空间的内容覆盖到模式空间中,G命令则是追加的方式。最后,x命令是交换两个空间的内容。
# 将第2行的内容复制到暂存空间,第5行的内容追加到暂存空间,最后扫描到最后一行时,将暂存的空间的内容复制回模式空间
# 由于sed会自动输出模式空间的行内容,所以最后会额外再输出第2行和第5行的内容
# 注意,$G 如果使用小写的g,则会造成最后一行被覆盖的情况,也就是最后一行不会被输出来
less test.txt | sed '2h;5H;$G'
# 等同于下面这条语句
less test.txt | sed -e '2h' -e '5H' -e '$G'
# 输出
1. name,age,sex,sss,aaa,www
2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
2. hello world
5. public class aaa
# 遇到包含today的行时,先复制到暂存空间。直到遇到包含class的行时,就把暂存空间的内容取出来替代这一行
sed -e '/today/h' -e '/class/x' test.txt
三、sed 支持的正则表达式
sed各个版本对正则的支持好像不太一样。比如本人用的mac上的sed,有些正则不能匹配,放在centos上执行却可以匹配上。
# 在centos上输出为 10.10 。但是在mac上输出为空
echo "10.10" | sed -n '/[0-9]\+.[0-9]\+/p'
# 下面这个命令在mac和centos上都可以输出
echo "10.10" | sed -n '/[0-9]\{1,\}\.[0-9]\{1,\}/p'
上面的例子可以看出,mac版本的sed对'\+'的无法识别,centos的sed却可以。所以我们最好使用'{1,}'来表示匹配1个到多个。另外,在sed中,如果要用'()'或者'{}'时,记得加上转义符''。如果只是用 '{1,}'则无法表示匹配1个到多个的情况,要写成'\{1,\}'才可以。正常要输出'(){}'符号时才不加转义符。这和我们平常用的正则不太一样
在sed中,像'\d' '\D' '\w' '\W' 最好不要用,因为sed的版本不一定支持。我们可以用[0-9] [^0-9] [a-z] [A-Z]等字符来替代效果。
下面的表格列出了所有版本的sed都支持的正则表达式元字符:
元字符 | 功能 | 示例 |
---|---|---|
^ | 行首定位符 | /^my/ 匹配所有以my开头的行 |
$ | 行尾定位符 | /my$/ 匹配所有以my结尾的行 |
. | 匹配除换行符以外的单个字符 | /m..y/ 匹配包含字母m,后跟两个任意字符,再跟字母y的行 |
* | 匹配零个或多个前导字符 | /my*/ 匹配包含字母m,后跟零个或多个y字母的行 |
[] | 匹配指定字符组内的任一字符 | /[Mm]y/ 匹配包含My或my的行 |
[^] | 匹配不在指定字符组内的任一字符 | /[^Mm]y/ 匹配包含y,但y之前的那个字符不是M或m的行 |
.... | 保存已匹配的字符 | 1,20s/youyouself/\1r/ 标记元字符之间的模式,并将其保存为标签1,之后可以使用\1来引用它。最多可以定义9个标签,从左边开始编号,最左边的是第一个。此例中,对第1到第20行进行处理,you被保存为标签1,如果发现youself,则替换为your。 |
& | 保存查找串以便在替换串中引用 | s/my/&/ 符号&代表查找串。my将被替换为my |
\< | 词首定位符 | /\ |
\> | 词尾定位符 | /my\>/ 匹配包含以my结尾的单词的行 |
x\{m\} | 连续m个x | /9\{5\}/匹配包含连续5个9的行 |
x\{m,\} | 至少m个x | /9\{5,\}/ 匹配包含至少连续5个9的行 |
x\{m,n\} | 至少m个,但不超过n个x | /9\{5,7\}/ 匹配包含连续5到7个9的行 |
四、分支和跳转
b命令 — 无条件跳转
我们可以通过:label
来定义分支,之后通过b命令来跳转到该分支处执行具体的命令。跳转到目标分支后中间的命令会被跳过。
定义分支的语法:
:label;[命令];
# 定义了一个label分支,用于输出行内容。
sed ':label;p'
示例:
# /hello/b Print 表示匹配到hello字段就跳转到 Print分支处。Print分支定义了 p命令,也就是用于输出这一行的内容
# /name/b alter 表示匹配到ok字段就跳转到 alter分支处。alter分支定义了s命令,用于修改内容并输出
# b no 如果前面的两个都没匹配到,就跳转到 no分支去,也就是什么都不做
less test.txt| sed -n '/hello/b Print;/name/b alter;b no;:Print;p;:alter;s/name/123/p;:no;'
# 输出
1. 123,age,sex
2. hello world
4. my 123 is jack
注意,上面的命令如果没有定义no分支并跳转的话,没匹配到Print分支和alter分支的行会继续往下执行命令,也就是执行 p命令和 's/name/123/p' 命令。
less test.txt| sed -n '/hello/b Print;/name/b alter;:Print;p;:alter;s/name/123/p;'
# 输出
1. 123,age,sex
2. hello world
3. today is 10.10
4. my 123 is jack
5. public class aaa
6. ok
t 命令 — 满足条件才跳转
我们也可以使用t命令来跳转到某个分支。和b命令不同的是,t命令必须在前一个置换(s)命令被执行成功后才跳转。我们将上面的那个b命令换成t命令看一下会输出什么
less test.txt| sed -n '/hello/t Print;/name/t alter;t no;:Print;p;:alter;s/name/123/p;:no;'
# 输出
1. name,age,sex
1. 123,age,sex
2. hello world
3. today is 10.10
4. my name is jack
4. my 123 is jack
5. public class aaa
6. ok
# 上面的命令等同于
less test.txt| sed -n 'p;s/name/123/p'
我们看到完全不同的输出。由于/hello/t Print;/name/t alter;
之前没有任何置换命令被执行成功,所以这几个跳转都不会生效,sed会直接往下执行。
t命令的实际应用
# 往包含到 hello所在的行插入 -----
less test.txt| sed -n ':Loop;/hello/s/^/-/;/-----/!t Loop;p'
# 输出
1. name,age,sex
-----2. hello world
3. today is 10.10
4. my name is jack
5. public class aaa
6. ok
上面的执行块我们可以分为4个部分看::Loop
/hello/s/^/-/
/-----/!t Loop
p
。第一块定义了一个标签Loop,后面第二块表示如果匹配到hello的行,就往前面添加一个'-',这时说明置换成功,则会触发后面的t命令生效。执行到第三块时,由于前面t命令只有在前面有置换命令成功才会跳转,所以第三块的命令只会对包含hello的行起作用。然后不断重复往行首添加'-',直到该行行首的字符满足'——'。
五、sed 脚本使用
通过 -f scriptfile
来将sed执行语句放到脚本中。我们可以编写一个sed脚本test.awk
。
/hello/p
/today/p
之后执行
sed -n -f test.sed test.txt
等同于执行
sed -n '/hello/p;/today/p'
awk脚本也可以这么写
#!/bin/sed -f
/hello/p
/today/p
之后直接执行该脚本即可
./test.sed -n test.txt
六、结尾
sed的内容还是很多的,本篇博客也只是简单的介绍了下各个命令的使用以及sed的一些概念。读者朋友们也不要因为这么多的内容而放弃学习sed。sed真的是一款功能强大的编辑器,非常推荐学习。
其实,我们只要对sed的关键语法以及一些常用的命令有所了解就足够了。我们的大多数使用场景可能也就用用s命令、d命令、p命令这些。因此,想快速学习sed的同学建议仔细看完第二节的第一小节,然后挑几个自己感兴趣的命令看就足够了。