Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程

sed 是一个比较古老的,功能十分强大的用于文本处理的流编辑器,加上正则表达式的支持,可以进行大量的复杂的文本编辑操作。sed 本身是一个非常复杂的工具,有专门的书籍讲解 sed 的具体用法,但是个人觉得没有必要去学习它的每个细节,那样没有特别大的实际意义。网上也有很多关于 sed 的教程,我也是抱着学习的心态来学习 sed 的常见的用法,并进行系统的总结,内容基本覆盖了 sed 的大部分的知识点。文中的内容比较简练,加以实际示例来帮助去理解 sed 的使用。

一、写在前边

1、sed介绍

sed 全名为 stream editor,流编辑器,用程序的方式来编辑文本,功能相当的强大。是贝尔实验室的 Lee E.McMahon 在 1973 年到 1974 年之间开发完成,目前可以在大多数操作系统中使用,sed 的出现作为 grep 的继任者。与vim等编辑器不同,sed 是一种非交互式编辑器(即用户不必参与编辑过程),它使用预先设定好的编辑指令对输入的文本进行编辑,完成之后再输出编辑结构。sed 基本上就是在玩正则模式匹配,所以,玩sed的人,正则表达式一般都比较强。

2、sed工作原理

sed会一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,成为”模式空间”,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

3、正则表达式概念

在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具,换句话说,正则表达式就是记录文本规则的代码。许多程序设计语言都支持利用正则表达式进行字符串操作。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。

4、正则表达式的匹配过程

简单描述一下正则表达式的匹配过程,就是拿正则表达式所表示的字符串去和原文字符串内容去匹配,直到匹配到原文内容字符串中的一个完整子串就表示匹配成功。举个例子,有一行文件内容”this is better desk”,这里用”esk”去匹配,匹配过程是这样的:首先拿e去匹配文件行内容,从this开始,直到better的e,第一个字符匹配成功,接着s去匹配better字符e后边的t字符,没有匹配成功;然后重新拿esk中的e去和better的第二个t去匹配,没有成功,接着原始内容的下一个字符,直到desk中的e字符,逐个匹配s,k字符,到此为止,esk成功匹配,正则表达式匹配完毕,整个过程就是这样,即使再复杂的正则表达式的匹配过程也是按照此过程来进行的。

5、语法

sed [-hnV][-e<script>][-f<script文件>][文本文件]

参数说明

  • -e<script>或–expression=<script> 以选项中指定的script来处理输入的文本文件。
  • -f<script文件>或–file=<script文件> 以选项中指定的script文件来处理输入的文本文件。
  • -h或–help 显示帮助。
  • -n或–quiet或–silent 仅显示script处理后的结果。
  • -V或–version 显示版本信息。

动作说明

  • a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
  • c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
  • d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
  • i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
  • p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
  • s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!

二、基本正则表达式

关于正则表达式的内容挺多的,掌握好下文中提及的内容就能满足正常工作中的需要,如果是专门做正则编程的,可以去买本正则表达式的书籍来看好了^_^。只有多动手多练习,才是学开发编程的最好姿势。

1. 符号”.”

匹配任意一个字符,除了换行符,但是需要注意的是,在sed中不能匹配换行符,但是在awk中可以匹配换行符。类似shell通配符中的”?”,匹配一个任意字符。

2. 符号”*”

“*”表示前边字符有0个或多个。”.*”表示任意一个字符有0个或多个,也就是能匹配任意的字符。类似shell通配符中的”*”,可以匹配任意字符。

3. 符号”[]”

"[ ]"中括号中可以包含表示字符集的表达式。使用方法大概有如下几种。
[a-z]:表示a-z字符中的一个,也就是小写字母。
[0-9]:表示0-9字符中的一个,也就是表示数字。
[A-Z]:表示大写字母。
[a-zA-Z]:表示字符集为小写字母或者大写字母。
[a-zA-Z0-9]:表示普通字符,包括大小写字母和数字。
[abc]:表示字符a或者字符b或者字符c。
[^0-9]:表示非数字类型的字符,^表示取反意思,只能放在中括号的开始处才有意义。
[-cz]:表示字符-或者字符c或者字符z,注意与[c-z]的区别,因为-符号没有放在e和f之间。

4. 符号”^”

“^”表示行首的意思,也就是每一行的开始位置。在这里并不是上边字符范围中取反的意思,^符号只有在”[]”符号的开头处才能表示字符取反。

^abc:表示以abc开头的字符串abc。
^abc.*:表示以abc开头的字符串abcxxx。

5. 符号”$”

“$”表示行尾的意思,也就是每一行的结尾位置,很好理解,和”^”正好相反。

world$:表示以world结尾的字符串world,如果该行中间有world字符串是不符合匹配条件的。
^$:表示空行。行首和行尾没有内容,可不就是空行嘛。

6. 符号”\”

“\”表示是转义字符,和其它语言中用到的转义字符意义基本上是一样的。其实简单理解,就是把元字符转义为普通字符,比如”\\”表示普通符号”\”,把普通字符转换为特殊意义符号,比如”\n”表示把普通字符n转义为换行符。

7. 符号”{}”

“{}”表示前边字符的数量范围,大概有三种用法,其实容易理解,看例子就知道了,但是必须注意要加上转义字符”\”,否则不生效,表示为普通字符”{“或”}”。

\{2\}:表示前边字符的重复次数是2。
\{2,\}:表示前边字符的重复次数至少是2,也就是大于等于2。
\{2,9\}:表示前边字符的重复次数大于2但小于9。

8. 符号”\<“和”\>”

“\<“表示匹配条件为词首的位置,理解上可以对比 “^” 行首。
举个例子,”nihao 1hello 2hello3 hello4″有这么内容的一行内容。
“\<hello”匹配结果”nihao 1hello 2hello3 hello4″;
“hello\>”匹配结果”nihao 1hello 2hello3 hello4″,这种匹配方式用的不是太多,用到会用就OK。

三、扩展正则表达式

扩展正则表达式是在基本正则表达式中扩展出来的,内容不是很多,使用频率上可能没有基本正则表达式那么高,但是扩展正则依然很重要,很多情况下没有扩展正则是搞不定的。sed命令使用扩展正则需要加上选项-r。

1. 符号”?”

“?”:表示前置字符有0个或1个。

2. 符号”+”

“+”:表示前置字符有1个或多个。

3. 符号”|”

“|”:表示指明两项之间的一个选择。
abc|ABC:表示可以匹配abc或者ABC。

4. 符号”()”

“()”表示分组,类似算数表达式中的()。子命令表达式中可以通过\1,\2,\3等来表示分组匹配到的内容。其实”()”也可以在基本正则表达式中使用的。

(a|b)b:表示可以匹配ab或者bb字串
([0-9])|([0][0-9])|([1][0-9]):表示匹配0-9或者00-09或者10-19范围的字符。

5. 符号”{}”

这里的”{}”和基本正则表达式中的大括号意义是一样的,只不过在使用时不用加”\”转义符号。

四、正则表达式的分类和应用

字符类
[Ww]hat  \.H[12345]

字符的范围
[a-z] [0-9] [Cc]hapter[1-9] [-+*/] [0-1][0-9][-/][0-3][0-9][-/][0-9][0-9]

排除字符类
[^0-9]

重复出现的字符
[15]0*  [15]00

字符的跨度
*与\{n,m\}

电话号码的匹配
[0-9]\{3\}-[0-9]\{7,8\}

分组操作
compan(y|ies)

五、sed语法和常用选项

1、语法

sed [选项] ‘command’ 文件名称
选项部分,常见选项包括-n,-e,-i,-f,-r选项。
command部分包括:[地址1,地址2] [函数] [参数(标记)]

2、常用选项

选项-n

sed默认会把模式空间处理完毕后的内容输出到标准输出,也就是输出到屏幕上,加上-n选项后被设定为安静模式,也就是不会输出默认打印信息,除非子命令中特别指定打印选项,则只会把匹配修改的行进行打印。

例子1:

echo -e 'hello world\nnihao' | sed 's/hello/A/'
结果:
A world
nihao

例子2:

echo -e 'hello world\nnihao' | sed -n 's/hello/A/'
结果:加-n选项后什么也没有显示。

例子3:

echo -e 'hello world\nnihao' | sed -n 's/hello/A/p'
结果:A world/
说明:-n选项后,再加p标记,只会把匹配并修改的内容打印了出来。

选项-e

如果需要用sed对文本内容进行多种操作,则需要执行多条子命令来进行操作。

例子1:

echo -e 'hello world' | sed -e 's/hello/A/' -e 's/world/B/'
结果:A B

例子2:

echo -e 'hello world' | sed 's/hello/A/;s/world/B/'
结果:A B

说明:例子1和例子2的写法的作用完全等同,可以根据喜好来选择,如果需要的子命令操作比较多的时候,无论是选择-e选项方式,还是选择分号的方式,都会使命令显得臃肿不堪,此时使用-f选项来指定脚本文件来执行各种操作会比较清晰明了。

选项-i

sed默认会把输入行读取到模式空间,简单理解就是一个内存缓冲区,sed子命令处理的内容是模式空间中的内容,而非直接处理文件内容。因此在sed修改模式空间内容之后,并非直接写入修改输入文件,而是打印输出到标准输出。如果需要修改输入文件,那么就可以指定-i选项。

例子1:

cat file.txt
hello world
[root@localhost]# sed 's/hello/A/' file.txt
A world
[root@localhost]# cat file.txt
hello world

例子2:

[root@localhost]# sed -i 's/hello/A/' file.txt
[root@localhost]# cat file.txt
A world

例子3:

[root@localhost]# sed –i.bak 's/hello/A/' file.txt

说明:最后一个例子会把修改内容保存到file.txt,同时会以file.txt.bak文件备份原来未修改文件内容,以确保原始文件内容安全性,防止错误操作而无法恢复原来内容。

选项-f

还记得 -e 选项可以来执行多个子命令操作,用分号分隔多个命令操作也是可以的,如果命令操作比较多的时候就会比较麻烦,这时候把多个子命令操作写入脚本文件,然后使用 -f 选项来指定该脚本。
例子1:

echo "hello world" | sed -f sed.script
结果:A B

sed.script脚本内容:

s/hello/A/
s/world/B/

说明:在脚本文件中的子命令串就不需要输入单引号了。

选项-r

sed命令的匹配模式支持正则表达式的,默认只能支持基本正则表达式,如果需要支持扩展正则表达式,那么需要添加-r选项。
例子1:

echo "hello world" | sed -r 's/(hello)|(world)/A/g'
A A

六、数字定址和正则定址

1、关于定址的概念

默认情况下sed会对每一行内容进行匹配、处理、输出,某些情况不需要对处理的文本全部编辑,只需要其中的一部分,比如1-10行,偶数行,或者是包含”hello”字符串的行,这种情况下就需要我们去定位特定的行来处理,而不是全部内容,这里把这个定位指定的行叫做”定址”。

2、数字定址

数字定址其实就是通过数字去指定具体要操作编辑的行,数字定址有几种方式,每种方式都有不同的应用场景,下边以举例的方式来描述每种数字定址的用法。

例子1:

sed –n '4s/hello/A/' message

说明:将第4行中hello字符串替换为A,其它行如果有hello也不会被替换。

例子2:

sed –n '2,4s/hello/A/' message

说明:将第2-4行中hello字符串替换为A,其它行如果有hello也不会被替换。

例子3:

sed –n '2,+4s/hello/A/' message

说明:从第2行开始,再接着往下数4行,也就是2-6行,这些行会把hello字符替换为A。

例子4:

sed –n '4,~3s/hello/A/' message

说明:第4行开始,到第6行。解释6的由来,”4,~3″表示从4行开始到下一个3的倍数,这里从4开始算,那就是6了,当然9就不是了,因为是要求3的第一个超过前边数字4的倍数,感觉这种适用场景不会太多。

例子5:

sed –n '4~3s/hello/A/' message

说明:从第4行开始,每隔3行就把hello替换为A。比如从4行开始,7行,10行等依次+3行。这个比较常用,比如3替换为2的时候,也就是每隔2行的步调,可以实现奇数和偶数行的操作。

例子6:

sed –n '$s/hello/A/' message

说明:$符号表示最后一行,和正则中的$符号类似,但是第1行不用^表示,直接1就行了。

例子7:

sed -n '1!s/hello/A/' message

说明:!符号表示取反,该命令是将除了第1行,其它行hello替换为A,上述定址方式也可以使用!符号。

3、正则定址

正则定址使用目的和数字定址完全一样,使用方式上有所不同,是通过正则表达式的匹配来确定需要处理编辑哪些行,其它行就不需要额外处理。

例子1:

sed -n '/nihao/d' message
说明:将匹配到nihao的行执行删除操作。

例子2:

sed -n '/^$/d' message
说明:删除空行

例子3:

sed -n '/^TS/,/^TE/d' message
说明:匹配以TS开头的行到TE开头的行之间的行,把匹配到的这些行删除。

4、数字定址和正则定址混用

其实数字定址和正则定址可以配合使用,参考下边的例子。

例子1:

sed -n '1,/^TS/d' message
说明:匹配从第1行到TS开头的行,把匹配的行删除。

5、关于定址的分组命令

例子1:

/^TS/,/^TE/{
s/CN/China/
s/Beijing/BJ/
}

说明:该命令表示将从TS开头的行到TE开头的行之间范围的行内容中CN替换为China,并且把Beijing替换为BJ,类似于多命令之间用分号的那种方式,不过这样定址代码只写了一遍,相当于执行了一条子命令。

例子2:

sed -n '2,3s{/cn/china/;/a/b/}' message

说明:效果类似例子1,有点数学上的乘法分配率的意思。

6、sed定址的总结

sed 默认的命令执行范围是全局编辑的,如果不明确指定行的话,命令会在所有输入行上执行,如果想仅对其中部分行执行命令,可以使用地址限制。如果给了 2 个地址,即地址对(地址范围),则命令匹配的这个地址范围内执行,但是需要注意的是:对于像 “addr1,addr2” 这种形式的地址匹配,如果addr1 匹配,则匹配成功,”开关”打开,在该行上执行命令,此时不管 addr2 是否匹配,即使 addr2 在 addr1 这一行之前;接下来读入下一行,如果addr2 匹配,则执行命令,同样开关”关闭”;如果 addr2 在 addr1 之后,则一直处理到匹配为止,换句话说,如果 addr2 一直不匹配,则开关一直不关闭,因此会持续执行命令到最后一行。

七、基本子命令

1、子命令a

子命令a表示在指定行下边插入指定行的内容。

例子1:

sed 'a A' message
说明:将message文件中每一行下边都插入添加一行内容是A。

例子2:

sed '1,2a A' message
说明:将message文件中1-2行的下边插入添加一行内容是A

例子3:

sed '1,2a A\nB\nC' message
说明:将message文件中1-2行的下边分别添加3行,3行内容分别是A、B、C,这里使用了\n,插入多行内容都可以按照这种方式来实现。

2、子命令i

子命令i和a使用上基本上一样,只不过是在指定行上边插入指定行的内容。

例子1:

sed 'i A' message
说明:将message文件中每一行上边都插入添加一行内容是A。

例子2:

sed '1,2i A' message
说明:将message文件中1-2行的上边插入添加一行内容是A

例子3:

sed '1,2i A\nB\nC' message
说明:将message文件中1-2行的上边分别添加3行,3行内容分别是A、B、C,这里使用了\n,插入多行内容都可以按照这种方式来实现。

3、子命令c

子命令c是表示把指定的行内容替换为自己需要的行内容。

例子1:

sed 'c A' message
说明:将message文件中所有的行内容都分别替换为A行内容。

例子2:

sed '1,2c A' message

说明:将message文件中1-2行的内容替换为A,注意这里说的是将1-2行所有的内容只替换为一个A内容,也就是1-2行内容编程了一行,定址如果连续就是这种情况。如果想把1-2行分别替换为A,可以参考下个例子的方式。

例子3:

sed '1,2c A\nA' message
说明:将message中1-2行内容分别替换为了A,需要在替换内容上手动加换行\n,这样当然也可以将一行内容替换为多行内容。

4、子命令d

子命令d表示删除指定的行内容,比较简单,更容易理解。

例子1:

sed 'd' message
说明:将message所有行全部删除,因为没有加定址表达式,所以平时如果需要删除指定行内容,需要在子命令前加定址表达式。

例子2:

sed '1,3d' message
说明:将message文件中1-3行内容删除。

5、子命令y

子命令y表示字符替换,可以替换多个字符,只能替换字符不能替换字符串,且不支持正则表达式,具体使用方法看例子。

例子1:

sed 'y/ab/AB/' message
说明:把message中所有a字符替换为A符号,所有b字符替换为B符号。

强调一下,这里的替换源字符个数和目的字符个数必须相等;字符不支持正则表达式;源字符和目标字符每个字符需要一一对应。

6、子命令=

子命令=,可以将行号打印出来。
例子:

sed '1,2=' message

结果:

1
nihao
2
hello world
说明:将指定行的上边显示行号。

7、子命令r

子命令r,类似于a,也是将内容追加到指定行的后边,只不过r是将指定文件内容读取并追加到指定行下边。

例子1:

sed '2r a.txt' message
说明:将a.txt文件内容读取并插入到message文件第2行的下边。

8、子命令s

子命令s为替换子命令,是平时sed使用的最多的子命令,没有之一。因为支持正则表达式,功能变得强大无比,下边来详细地说说子命令s的使用方法。

基本语法:
[address]s/pattern/replacement/flags

s字符串替换,替换的时候可以把/换成其它的符号,比如=,replacement部分用下列字符会有特殊含义:

>>>  &:用正则表达式匹配的内容进行替换
>>>  \n:回调参数
>>>  \(\):保存被匹配的字符以备反向引用\n时使用,最多9个标签,标签书序从左到右

Flags

>>>  n:可以是1-512,表示第n次出现的情况进行替换
>>>  g:全局更改
>>>  p:打印模式空间的内容
>>>  w file:写入到一个文件file中

实例用法

测试文件:

# cat message
hello 123 world

例子1:

sed 's/hello/HELLO/' message
说明:将message每行包含的第一个hello的字符串替换为HELLO,这是最基本的用法。

例子2:

sed -r 's/[a-z]+ [0-9]+ [a-z]+/A/' message
结果:A
说明:使用了扩展正则表达式,需要加-r选项。

例子3:

sed -r 's/([a-z]+)( [0-9]+ )([a-z]+)/\1\2\3/' message
结果:hello 123 world
说明:再看下一个例子就明白了。

例子4:

sed -r 's/([a-z]+)( [0-9]+ )([a-z]+)/\3\2\1/' message
结果:world 123 hello
说明:\1表示正则第一个分组结果,\2表示正则匹配第二个分组结果,\3表示正则匹配第三个分组结果。

例子5:

sed -r 's/([a-z]+)( [0-9]+ )([a-z]+)/&/' message
结果:hello 123 world
说明:&表示正则表达式匹配的整个结果集。

例子6:

sed -r 's/([a-z]+)( [0-9]+ )([a-z]+)/111&222/' message
结果:111hello 123 world222
说明:在匹配结果前后分别加了111、222。

例子7:

sed -r 's/.*/111&222/' message
说明:在message文件中每行的首尾分别加上111、222。

例子8:

sed 's/i/A/g' message
说明:把message文件中每行的所有i字符替换为A,默认不加g标记时只替换每行的第一个字符。

例子9:

sed 's/i/A/2' message
说明:把message文件中每行的第2个i字符替换为A。

例子10:

sed -n 's/i/A/p' message
说明:加-p标记会把被替换的行打印出来,再加上-n选项会关闭模式空间打印模式,因此该命令的效果就是只显示被替换修改的行。

例子11:

sed -n 's/i/A/w b.txt' message
说明:把message文件中内容的每行第一个字符i替换为A,然后把修改内容另存为b.txt文件。

例子12:

sed -n 's/i/A/i' message
说明:把message文件中每一行的第一个i或I字符替换为A字符,也即是忽略大小写。

八、sed工作模式

1、模式空间和保持空间

模式空间初始化为空,处理完一行后会自动输出到屏幕并清除模式空间;保持空间初始化为一个空行,也就是默认带一个\n,处理完后不会自动清除。模式空间和保持空间,从程序的角度去看,其实就是sed在工作的时候占用了一些内存空间和地址,sed工作完毕就会把内存释放并归还给操作系统。

2、sed工作流程

大概简单描述一下sed的工作流程,读取文件的一行,存入模式空间,然后进行所有子命令的处理,处理完后默认会将模式空间的内容输出打印到标准输出,也就是在屏幕上显示出来,接着清空模式空间的内存,继续读取下一行的内容到模式空间,继续处理,依次循环处理。

3、模式空间和保持空间的置换

h:把模式空间内容覆盖到保持空间中
H:把模式空间内容追加到保持空间中
g:把保持空间内容覆盖到模式空间中
G:把保持空间内容追加到模式空间中
x:交换模式空间与保持空间的内容

4、实例用法

测试文件:

# cat test.txt
11111
22222
33333
44444

例子1:

sed '{1h;2,3H;4G}' test.txt
#结果:
11111
22222
33333
44444
11111
22222
33333
解释说明:略。懒得写了。

例子2:

sed '{1h;2x;3g;$G}' test.txt
#结果:
11111
11111
22222
44444
22222
解释说明:略。

例子3:

sed '{1!G;h;$!d}' test.txt
#结果:
44444
33333
22222
11111

九、高级子命令

高级子命令比较少,但是比较复杂,平时用的也会相对少些,却也很重要,有的内容处理不用高级子命令是完成不了的。

n:读入下一行到模式空间,例:’4{n;d}’ 删除第5行。
N:追加下一行到模式空间,再把当前行和下一行同时应用后面的命令。
P:输出多行模式空间的第一部分,直到第一个嵌入的换行符位置。在执行完脚本的最后一个命令之后,模式空间的内容自动输出。P命令经常出现在N命令之后和D命令之前。
D:删除模式空间中第一个换行符的内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用与模式空间剩余的内容。这3个命令能建立一个输入、输出循环,用来维护两行模式空间,但是一次只输出一行。

例子1:

sed 'N;$!P;D' a.txt
#说明:删除文件倒数第二行

例子2:

sed 'N;$!P;$!D;$d' a.txt
# 说明:删除文件最后两行

十、分支和测试

分支命令用于无条件转移,测试命令用于有条件转移。

1、分支branch
跳转的位置与标签相关联。
如果有标签则跳转到标签所在的后面行继续执行。
如果没有标签则跳转到脚本的结尾处。
标签:以冒号开始后接标签名,不要在标签名前后使用空格。

2、跳转到标签指定位置
测试文件:

grep seker /etc/passwd
seker:x:500:500::/home/seker:/bin/bash

例子1:

grep seker /etc/passwd | sed ':top;s/seker/blues/;/seker/b top;s/5/555/'
结果:blues:x:55500:500::/home/blues:/bin/bash

选择执行
例子2:

grep 'seker' /etc/passwd | sed 's/seker/blues/;/seker/b end;s/5/555/;:end;s/5/666/'
结果:blues:x:66600:500::/home/seker:/bin/bash

测试命令,如果前一个替换命令执行成功则跳转到脚本末尾(case结构)
例子3:

grep 'seker' /etc/passwd | sed 's/seker/ABC;t;s/home/DEF/;t;s/bash/XYZ/'
结果:ABC:x:500:500::/home/seker:/bin/bash

例子4:

grep 'zorro' /etc/passwd | sed 's/seker/ABC/;t;s/home/DEF/;t;s/bash/XYZ'
结果:zorro:x:500:500::/DEF/zorro:/bin/bash

与标签关联,跳转到标签位置。
例子5:

grep 'seker' /etc/passwd | sed 's/seker/ABC/;t end;s/home/DEF/;t;end;s/bash/XYZ'
结果:ABC:x:500:500::/home/seker:/bin/XYZ

十一、sed实战练习

实例1:删除文件每行的第二个字符。

sed -r 's/(.*)(.)$/\1/'

实例2:删除文件每行的最后一个字符。

sed -r 's/(.*)(.)$/\1/'

实例3:删除文件每行的倒数第2个单词。

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

实例4:交换每行的第一个字符和第二个字符。

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

实例5:交换每行的第一个单词和最后一个单词。

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

实例6:删除一个文件中所有的数字。

sed 's/[0-9]//g' /etc/passwd

实例7:用制表符替换文件中出现的所有空格。

sed -r 's/ +/\t/g' /etc/passwd

实例8:把所有大写字母用括号()括起来。

sed -r 's/([A-Z])/(\1)/g' /etc/passwd

实例9:打印每行3次。

sed 'p;p' /etc/passwd

实例10:隔行删除

sed '0~2{=;d}' /etc/passwd

实例11:把文件从第22行到第33行复制到56行后面。

sed '22h;23,33H;56G' /etc/passwd

实例12:把文件从第22行到第33行移动到第56行后面。

sed '22{h;d};23,33{H;d};56g' /etc/passwd

实例13:只显示每行的第一个单词。

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

实例14:打印每行的第一个单词和第三个单词。

sed -r 's/([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)(.*)/\1\t\5/' /etc/passwd

实例15:将格式为mm/yy/dd的日期格式换成 mm;yy;dd

date '+%m/%y/%d' | sed 's/\//;/g'

 

用s命令替换

我使用下面的这段文本做演示:

$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

把其中的my字符串替换成Hao Chen’s,下面的语句应该很好理解(s表示替换命令,/my/表示匹配my,/Hao Chen’s/表示把匹配替换成Hao Chen’s,/g 表示一行上的替换所有的匹配):

$ sed "s/my/Hao Chen's/g" pets.txt
This is Hao Chen's cat
  Hao Chen's cat's name is betty
This is Hao Chen's dog
  Hao Chen's dog's name is frank
This is Hao Chen's fish
  Hao Chen's fish's name is george
This is Hao Chen's goat
  Hao Chen's goat's name is adam

注意:如果你要使用单引号,那么你没办法通过\’这样来转义,就有双引号就可以了,在双引号内可以用\”来转义。

再注意:上面的sed并没有对文件的内容改变,只是把处理过后的内容输出,如果你要写回文件,你可以使用重定向,如:

$ sed "s/my/Hao Chen's/g" pets.txt > hao_pets.txt

或使用 -i 参数直接修改文件内容:

$ sed -i "s/my/Hao Chen's/g" pets.txt

在每一行最前面加点东西:

$ sed 's/^/#/g' pets.txt
#This is my cat
#  my cat's name is betty
#This is my dog
#  my dog's name is frank
#This is my fish
#  my fish's name is george
#This is my goat
#  my goat's name is adam

在每一行最后面加点东西:

$ sed 's/$/ --- /g' pets.txt
This is my cat ---
  my cat's name is betty ---
This is my dog ---
  my dog's name is frank ---
This is my fish ---
  my fish's name is george ---
This is my goat ---
  my goat's name is adam ---

顺手介绍一下正则表达式的一些最基本的东西:

  • ^ 表示一行的开头。如:/^#/ 以#开头的匹配。
  • $ 表示一行的结尾。如:/}$/ 以}结尾的匹配。
  • \< 表示词首。 如:\<abc 表示以 abc 为首的詞。
  • \> 表示词尾。 如:abc\> 表示以 abc 結尾的詞。
  • . 表示任何单个字符。
  • * 表示某个字符出现了0次或多次。
  • [ ] 字符集合。 如:[abc] 表示匹配a或b或c,还有 [a-zA-Z] 表示匹配所有的26个字符。如果其中有^表示反,如 [^a] 表示非a的字符

正规则表达式是一些很牛的事,比如我们要去掉某html中的tags:

HTML.TXT

<b>This</b> is what <span style="text-decoration: underline;">I</span> meant. Understand?

看看我们的sed命令

# 如果你这样搞的话,就会有问题
$ sed 's/<.*>//g' html.txt
 Understand?
 
# 要解决上面的那个问题,就得像下面这样。
# 其中的'[^>]' 指定了除了>的字符重复0次或多次。
$ sed 's/<[^>]*>//g' html.txt
This is what I meant. Understand?

我们再来看看指定需要替换的内容:

$ sed "3s/my/your/g" pets.txt
This is my cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

下面的命令只替换第3到第6行的文本。

$ sed "3,6s/my/your/g" pets.txt
This is my cat
  my cat's name is betty
This is your dog
  your dog's name is frank
This is your fish
  your fish's name is george
This is my goat
  my goat's name is adam

只替换每一行的第一个s:

$ sed 's/s/S/1' my.txt
ThiS is my cat, my cat's name is betty
ThiS is my dog, my dog's name is frank
ThiS is my fish, my fish's name is george
ThiS is my goat, my goat's name is adam

只替换每一行的第二个s:

$ sed 's/s/S/2' my.txt
This iS my cat, my cat's name is betty
This iS my dog, my dog's name is frank
This iS my fish, my fish's name is george
This iS my goat, my goat's name is adam

只替换第一行的第3个以后的s:

$ sed 's/s/S/3g' my.txt
This is my cat, my cat'S name iS betty
This is my dog, my dog'S name iS frank
This is my fiSh, my fiSh'S name iS george
This is my goat, my goat'S name iS adam

 

多个匹配

如果我们需要一次替换多个模式,可参看下面的示例:(第一个模式把第一行到第三行的my替换成your,第二个则把第3行以后的This替换成了That)

$ sed '1,3s/my/your/g; 3,$s/This/That/g' my.txt
This is your cat, your cat's name is betty
This is your dog, your dog's name is frank
That is your fish, your fish's name is george
That is my goat, my goat's name is adam

上面的命令等价于:(注:下面使用的是sed的-e命令行参数)

sed -e '1,3s/my/your/g' -e '3,$s/This/That/g' my.txt

我们可以使用&来当做被匹配的变量,然后可以在基本左右加点东西。如下所示:

$ sed 's/my/[&]/g' my.txt
This is [my] cat, [my] cat's name is betty
This is [my] dog, [my] dog's name is frank
This is [my] fish, [my] fish's name is george
This is [my] goat, [my] goat's name is adam

 

圆括号匹配

使用圆括号匹配的示例:(圆括号括起来的正则表达式所匹配的字符串会可以当成变量来使用,sed中使用的是\1,\2…)

$ sed 's/This is my \([^,&]*\),.*is \(.*\)/\1:\2/g' my.txt
cat:betty
dog:frank
fish:george
goat:adam

上面这个例子中的正则表达式有点复杂,解开如下(去掉转义字符):

正则为:This is my ([^,]*),.*is (.*)
匹配为:This is my (cat),……….is (betty)

然后:\1就是cat,\2就是betty

sed的命令

让我们回到最一开始的例子pets.txt,让我们来看几个命令:

N命令

先来看N命令 —— 把下一行的内容纳入当成缓冲区做匹配。

下面的的示例会把原文本中的偶数行纳入奇数行匹配,而s只匹配并替换一次,所以,就成了下面的结果:

$ sed 'N;s/my/your/' pets.txt
This is your cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is your fish
  my fish's name is george
This is your goat
  my goat's name is adam

也就是说,原来的文件成了:

This is my cat\n  my cat's name is betty
This is my dog\n  my dog's name is frank
This is my fish\n  my fish's name is george
This is my goat\n  my goat's name is adam

这样一来,下面的例子你就明白了,

$ sed 'N;s/\n/,/' pets.txt
This is my cat,  my cat's name is betty
This is my dog,  my dog's name is frank
This is my fish,  my fish's name is george
This is my goat,  my goat's name is adam
p命令

打印命令

你可以把这个命令当成grep式的命令

# 匹配fish并输出,可以看到fish的那一行被打了两遍,
# 这是因为sed处理时会把处理的信息输出
$ sed '/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my fish, my fish's name is george
This is my goat, my goat's name is adam
 
# 使用n参数就好了
$ sed -n '/fish/p' my.txt
This is my fish, my fish's name is george
 
# 从一个模式到另一个模式
$ sed -n '/dog/,/fish/p' my.txt
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
 
#从第一行打印到匹配fish成功的那一行
$ sed -n '1,/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george

 

 

几个知识点

好了,下面我们要介绍四个sed的基本知识点:

Pattern Space

第零个是关于-n参数的,大家也许没看懂,没关系,我们来看一下sed处理文本的伪代码,并了解一下Pattern Space的概念:

foreach line in file {
    //放入把行Pattern_Space
    Pattern_Space <= line;
 
    // 对每个pattern space执行sed命令
    Pattern_Space <= EXEC(sed_cmd, Pattern_Space);
 
    // 如果没有指定 -n 则输出处理后的Pattern_Space
    if (sed option hasn't "-n")  {
       print Pattern_Space
    }
}
Address

第一个是关于address,几乎上述所有的命令都是这样的(注:其中的!表示匹配成功后是否执行命令)

[address[,address]][!]{cmd}

address可以是一个数字,也可以是一个模式,你可以通过逗号要分隔两个address 表示两个address的区间,参执行命令cmd,伪代码如下:

bool bexec = false
foreach line in file {
    if ( match(address1) ){
        bexec = true;
    }
 
    if ( bexec == true) {
        EXEC(sed_cmd);
    }
 
    if ( match (address2) ) {
        bexec = false;
    }
}

关于address可以使用相对位置,如:

# 其中的+3表示后面连续3行
$ sed '/dog/,+3s/^/# /g' pets.txt
This is my cat
  my cat's name is betty
# This is my dog
#   my dog's name is frank
# This is my fish
#   my fish's name is george
This is my goat
  my goat's name is adam
命令打包

第二个是cmd可以是多个,它们可以用分号分开,可以用大括号括起来作为嵌套命令。下面是几个例子:

$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam
 
# 对3行到第6行,执行命令/This/d
$ sed '3,6 {/This/d}' pets.txt
This is my cat
  my cat's name is betty
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam
 
# 对3行到第6行,匹配/This/成功后,再匹配/fish/,成功后执行d命令
$ sed '3,6 {/This/{/fish/d}}' pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam
 
# 从第一行到最后一行,如果匹配到This,则删除之;如果前面有空格,则去除空格
$ sed '1,${/This/d;s/^ *//g}' pets.txt
my cat's name is betty
my dog's name is frank
my fish's name is george
my goat's name is adam

 

Hold Space

第三个我们再来看一下 Hold Space

接下来,我们需要了解一下Hold Space的概念,我们先来看四个命令:

g: 将hold space中的内容拷贝到pattern space中,原来pattern space里的内容清除
G: 将hold space中的内容append到pattern space\n后
h: 将pattern space中的内容拷贝到hold space中,原来的hold space里的内容被清除
H: 将pattern space中的内容append到hold space\n后
x: 交换pattern space和hold space的内容

这些命令有什么用?我们来看两个示例吧,用到的示例文件是:

$ cat t.txt
one
two
three

第一个示例:

$ sed 'H;g' t.txt
one
 
one
two
 
one
two
three

是不是有点没看懂,我作个图你就看懂了。

Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程
Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程

第二个示例,反序了一个文件的行:

$ sed '1!G;h;$!d' t.txt
three
two
one

其中的 ‘1!G;h;$!d’ 可拆解为三个命令

  • 1!G —— 只有第一行不执行G命令,将hold space中的内容append回到pattern space
  • h —— 第一行都执行h命令,将pattern space中的内容拷贝到hold space中
  • $!d —— 除了最后一行不执行d命令,其它行都执行d命令,删除当前行

这个执行序列很难理解,做个图如下大家就明白了:

Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程
Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程

就先说这么多吧,希望对大家有用。

 

 

本文:Shell:SED 简明教程, 流编辑器 SED 全教程, sed入门详解教程

Leave a Reply