[Linux Shell学习系列十四]sed和awk-1.sed编辑器基础&2.基本的sed编辑指令
D27
sed和awk是我们处理文本文件的有力工具。
第一节 sed编辑器基础
sed是用来解析和转换文本的工具,它使用简单,是简洁的程序设计语言。
sed是最早的支持正则表达式的工具之一,并且仍用于文本处理,特别是替换指令。
1. sed简介
sed是非交互式的面向数据流的编辑器。之所以说它是面向数据流的,是因为像很多Unix程序程序一样,输入通过程序被重定向到标准输出。
输入通常来自文件,也可以来自键盘;输出默认是发送到终端屏幕,也可以重定向到文件。
sed可以通过解释脚本来工作,该脚本中指定了将要执行的动作。
使用sed做如下操作:
1)自动化的编辑一个或多个文件;
2)简化在多个文件中执行相同编辑的任务;
3)编写转换程序。
2. sed的模式空间
sed维护一种模式空间,即一个工作区或临时缓冲区,当使用编辑命令时,将在那里存储单个输入行。
注:sed一次处理一行输入的优点是在读取非常庞大的文件时不会出现问题。一般的文本编辑器必须将整个文件(或者它庞大的一部分)读入内存,这将会产生内存溢出或在处理庞大文件时速度非常慢。
初始时,模式空间包含单个输入行的备份,之后在这一行上执行每个命令。注意第二个替换指令不匹配最初的输入行,匹配模式空间中发生了变化的当前行。当应用了所有的指令后,当前行被输出且输入的下一行被读入模式空间。然后sed脚本中的所有命令应用于新读入的行。结果是:任何一个sed命令都可以为下一个命令修改模式空间的内容。模式空间的内容是动态的,而且并不总是匹配最初的输入行。
第二节 基本的sed编辑命令
两种语法:
1)在命令行指定你的sed指令:sed [OPTIONS]... 'COMMAND' [FILE]...
2)将sed指令放入一个文件中并将其文件名作为参数:sed [OPTIONS] -f SCRIPTFILE [FILE]...
常用的选项:
| 选项 | 说明 |
| -e |
告诉sed下一个参数解释为sed指令。在命令行上给出多个sed指令时才需要使用。 |
| -f | 指定由sed指令组成的脚本的名称。如果sed脚本的第一行为#n,则sed的行为与指定-n选项相同。 |
| -i | 直接修改读取的内容,而不是输出到终端。 |
| -n | 取消默认输出。一般sed用法中,所有来自标准输入的数据一般都会被显示到终端上;但使用-n选项厚,只有经过sed处理的行才会被显示输出。 |
sed指令的语法:
[address[,address]][!]command
sed指令由地址和编辑命令组成。如果指令的地址和模式空间中的行匹配,那么编辑命令就被应用于匹配的行。如果一个sed指令没有地址,那么将被应用于所有的行。如果一个编辑命令改变了模式空间的内容,后续的编辑命令的地址将被应用于模式空间中的当前行,而不是原始的输入行。
sed指令的地址对任何sed的编辑命令都是可选的,它可以是一个模式,被描述为由斜杠、行号或行寻址符号括住的正则表达式。大多数sed命令能接收由逗号分隔的两个地址,这两个地址用来标识行的范围。这些指令的语法格式为:
[address1, address2]command
有一些编辑命令只接收单个地址。不能应用于某个范围的行。语法格式为:
[line-address]command
编辑命令还可以用大括号进行分组以使其作用于同一个地址,语法格式为:
address {
command1
command2
command3
}
注意:上面语法中}右大括号必须自己单独处于一行,建议多个命令也不使用;放在同一行,也进行分行。
sed的编辑命令有24个,详细信息参考man手册。下面介绍几个。
1. 追加、更改、插入编辑命令
追加(a)、更改(c)、插入(i)编辑命令提供了类似于vi交互式命令编辑器的编辑功能。这些命令的语法在sed中不常用,因为它们必须在多行上来指定。
语法:
[line-address]a\
text
[line-address]c\
text
[line-address]i\
text
追加命令(a)将文本放在当前行之后。
更改命令(c)用所指定的文本取代模式空间的内容。
插入命令(i)将所提供的文本防止在模式空间的当前行之前。
上述命令都要求后面跟一个反斜杠用于转义第一个行尾,text必须从下一行开始。如要输入多行文本,每个连续的行都必须用反斜杠结束,最后一行除外。而且,如果文本包含一个字面意思的反斜杠,要再添加一个反斜杠来转义它。
示例:
#使用追加命令a在匹配行的地方插入两行内容 $ cat example.txt <Effy's info> <Tom's info> #sed脚本 $ cat sedInsert /<Tom's info>/a\ Full name: Tom Cruis\ Age: 50 #使用sed命令 $ sed -f sedInsert example.txt <Effy's info> <Tom's info> Full name: Tom Cruis Age: 50 #使用追加命令a在文件结尾处添加一行内容 $ cat example.txt <Effy's info> <Tom's info> #使用sed命令,这里的$是行寻址符号,匹配文件的最后一行。 $ sed '$a<End of file>' example.txt <Effy's info> <Tom's info> <End of file>
追加命令a和插入命令i都只应用于单个行地址,而不是一个范围内的行。而更改命令c可以处理一个范围内的行。此时,它用一个文本备份取代所有被寻址的行,换句话说,先删除所有范围内的行,但是提供的文本只输出一次。
#要处理的文本内容 $ cat mail.txt From: A@a.com To: Test subject: test #sed脚本文件 $ cat sedchange /^From /,/^$/c\ <Mail Header Removed> #使用sed命令 $ sed -f sedchange mail.txt <Mail Header Removed> To: Test subject: test
更改命令c会清除模式空间,它在模式空间中与删除命令有同样的效果。所以在sed脚本中,变更命令会被放在最后。
插入命令和追加命令不影响模式空间的内容。提供的文本将不匹配脚本中后续命令的任何地址,那些命令也不影响该文本。不过什么更高改变了模式空间,所提供的文本仍然会正确输出。同样,提供的文本不影响sed的内部行计数器。
#当前目录下.sh文件都在第一行添加"#!/bin/bash" $ sed -i li\#\!/bin/bash *.sh #sed执行该命令之后,模式空间不会修改。其中的新文本在当前行的前面输出,后续命令不能成功匹配"#!/bin/bash"
2. 删除编辑命令
删除编辑命令(d)采用一个地址,如果行匹配这个地址,就删除模式空间的内容。
删除命令(d)还是一个可以改变脚本中的控制流的命令。因为一旦执行这个命令,那么在“空的”模式空间就不会再有命令执行。
删除命令会导致读取新输入行,而编辑脚本则从头开始新的一轮。
重要的是,如果某行匹配这个地址,那么就是删除整个行,而不只是删除行中匹配的部分。
$ cat mail.txt From: A@a.com To: Test subject: test #sed命令删除文件中的空行 $ sed '/^$/d' mail.txt From: A@a.com To: Test subject: test
也可以删除一个范围内的行:
#删除文件中从第50行到最后一行的所有行 $ sed '50,$d' file
3. 替换编辑命令
替换编辑命令(s)的语法如下所示:
[address]s/pattern/replacement/flags
其中,flags是替换命令(s)的修饰标志,有:
| 取值 | 说明 | 备注 |
| n | 1~512之间的数字,表示对文本模式pattern中指定模式第n次出现的情况进行替换 | 仅在很少的情况下使用,比如正则表达式在一行上重复匹配,我们只对其中某个位置的匹配进行替换 |
| g | 对模式空间的所有出现的情况进行全局更改。而没有g时通常只有第一次出现的情况被更改 | 比较常用,没有它,替换只能在第一次出现的位置执行 |
| p | 打印模式空间的内容 |
与打印编辑命令功能相同,但有一个很重要的区别,这些标志是随替换的成功而发生的。换句话说,如果进行了替换,则这一行被打印或写到文件中;而默认的动作是处理所有航,不管是否执行了任何动作。 当sed取消默认输出(-n选项)时,通常会使用p和w标志 |
| w file | 将模式空间的内容写入到文件file中 | 与写编辑命令的功能相同,区别同上。 |
修饰标志可以组合使用,只要有意义,如:gp表示对行进行全局替换并打印这一行。
替换命令(s)应用于与地址(address)匹配的行。如果没有指定地址,那么就应用于模式(pattern)匹配的所有航。如果正则表达式作为地址来提供,并且没有指定模式,那么替换命令匹配由地址匹配的内容。当替换命令是应用于同一个地址上的多个命令之一时,这可能会非常有用。
地址需要一个作为界定符的斜杠/,和地址不同的是,正则表达式可以用任意字符来分隔,只有换行符(\n)除外。因此,如果模式包含斜杠,可以选择另一个字符作为定界符,例如感叹号:
s!/usr/lib!/usr/lib64!
注意:定界符出现了3次,而且在替代字符串(replacement)之后是必需的。不管使用那种定界符,如果它出现在正则表达式中,或者在替换文本中,就用反斜杠将它转义。
replacement是一个字符串,用来替换与模式(正则表达式)匹配的内容。在replacement部分,只有如下字符具有特殊的含义:
| 字符 | 含义 |
| & | 由正则表达式匹配的字符串进行替换 |
| \n | 匹配第n个字符串(n是一个数字),这个子字符串是先前在模式(pattern)中使用“\(”或“\)”指定的 |
| \ | 用于转义符号&和\等字符,另外用户转义换行符并创建多行replacement字符串 |
因此,除了正则表达式中的元字符以外,sed的替换部分(replacement)也有元字符。
$ cat file column1 column2 column3 column4 #sed脚本:用换行符取代第2个制表符 $ cat sedSubstitution s!\t!\n!2 #使用sed命令 $ sed -f sedSubstitution file column1 column2 column3 column4
replacement中&使用
1)转义为普通字符
s/ALU/Alcatel \& Lucent/g #此时文本中"ALU"被替换为"Alcatel & Lucent"
2)作为元字符表示模糊匹配的范围,而不是被匹配的行。&可以匹配一个单词或一个字符串。
如上面的脚本中不进行转义:
s/ALU/Alcatel & Lucent/g #此时文本中"ALU"被替换为"Alcatel ALU Lucent"
当正则表达式匹配单词的变化时,&符号特别有用。它允许指定一个可变的替换字符串,该字符串相当于匹配的内容与实际内容匹配的字符串。
$ cat README See Section 14.1 for the Key value pairs and see Section 9.2 for the locale codes. #通过正则表达式匹配数字的不同组合,在替换字符串中用&加括号,实现将匹配的内容用括号括起来的替换操作 $ sed 's/Section [1-9][0-9]*\.[1-9][0-9]*/( & )/g' README See ( Section 14.1 ) for the Key value pairs and see ( Section 9.2 ) for the locale codes.
4. 打印编辑命令
打印编辑命令(p)输出模式空间的内容,它既不清除模式空间也不改变脚本中的控制流。然而,它一般会被频繁的用在改变流控制的命令(比如:d、N、b)之前。除非使用-n选项关闭默认输出,否则打印命令将输出行的重复赋值。当使用-n选项关闭默认输出,或者当通过程序的流控制来避免达到脚本的底部时,也可能会用它。
#sed脚本 $ cat sedDebug /^Index/{
#这里直接打印 p
#这里替换-,但没有打印 s/-//
#这里替换Index 后打印 s/^Index //p } #待处理文件 $ cat index Index -Shell introduction Index -Shell beginning Index -Basic command Index -Advanced command #未使用-n选项 $ sed -f sedDebug index Index -Shell introduction Shell introduction Shell introduction Index -Shell beginning Shell beginning Shell beginning Index -Basic command Basic command Basic command Index -Advanced command Advanced command Advanced command #使用-n选项 $ sed -nf sedDebug index Index -Shell introduction Shell introduction Index -Shell beginning Shell beginning Index -Basic command Basic command Index -Advanced command Advanced command
注:打印标志被提供给替换命令。替换命令的打印标志不同于打印命令,因为它是以成功的替换为条件的。
5. 打印行号编辑命令
跟在地址后面的等号“=”用来打印被匹配的行的行号。除非抑制行的自动输出,行号和行本身都将被打印。
语法:
[line-address]=
但这个命令不能对一个范围内的行进行操作。
$ cat list.txt ab dde ab dde abde 333 a #sed脚本:以a开头的行 $ cat setPrintln /^a/{ = p } #sed命令 $ sed -nf setPrintln list.txt 1 ab dde 2 ab dde 5 abde 7 a
在查找由编辑器报告的问题时,行号是非常有用的,编译器通常会列出行号。
6. 读取下一行编辑命令
读取下遗憾干煸鸡命令(n)用于读取输入的下一行到模式空间。
语法:
[address]n
该命令改变了正常的流控制,导致输入的下一行取代模式空间中的当前行。脚本中的后续命令应用于替换后的行,而不是当前行。如果没有抑制默认输出,那么在替换发生之前会打印当前行。
#待处理文件 $ cat mail.txt From: A@a.com To: Test subject: test #sed脚本:匹配"From: "的行,然后读入下一行,如果新读入的行为空,则删除它 $ cat sedNext /From: /{ n /^$/d } #sed命令 $ sed -f sedNext mail.txt From: A@a.com To: Test subject: test
注意:出现在读取下一行命令(n)之前的命令不会应用于模式空间中新的输入行,而且出现在其后面的命令也不应用于模式空间中旧的输入行。
7. 读和写文件编辑命令
读文件编辑命令(r)和写文件编辑命令(w)用于直接处理文件。这两个命令都只有一个参数,即文件名。
语法:
[line-address]r file
[address]w file
读文件命令将由参数file所指定的文件的内容读入模式空间中匹配的行之后。它不能对一个范围内的行进行操作。
写文件命令将模式空间的内容写到参数file所指定的文件中。
在读写文件编辑命令和文件名之间必须有空格(空格后到换行符前的每个字符都被当作文件名。因此,前导的和嵌入的空格也是文件名的一部分)。如果文件不存在,读文件指令也不会报错。如果写文件命令中指定的文件不存在,将创建一个文件;如果文件已经存在,那么每次当脚本被调用时其中的写文件命令将改写它。
如果一个sed脚本中有多个指令会写到同一个文件中,那么每个写文件指令都将内容追加到这个文件中。
读文件命令对于将一个文件的内容插入到另一个文件的特定位置时是很有用的。
#文件endOfFile内容 $ cat endOfFile Contact us: A@a.com #sed指令:在后缀为.txt的文件末尾插入endOfFile的内容
#$是寻址符,匹配文件最后一行 $ sed -i '$r endOfFile' *.txt #修改后的文件内容 $ cat list.txt ab dde ab dde abde 333 a Contact us: A@a.com
读文件命令后面的编辑命令不会影响读文件命令从文件中读取的行。-n选项也不会取消读文件命令输出的内容。
$ cat tagContent This is first html script. Hello World! $ cat first.html <html> <body> <tag> </tag> </body> </html> #sed指令:将tagContent文件内容附加在<tag>开头的行末尾 $ sed '/^<tag>/r tagContent' first.html <html> <body> <tag> This is first html script. Hello World! </tag> </body> </html> #sed脚本:测试通过读文件命令附加的内容不会被后续的指令改变 $ cat sedTestReadFile /^<tag>/r tagContent /Hello World!/d #执行sed脚本:附加的内容没有被改变 $ sed -f sedTestReadFile first.html <html> <body> <tag> This is first html script. Hello World! </tag> </body> </html> #使用-n选项取消自动输出,阻止模式空间的初始化被输出,但是读命令的结果仍然会转到标准输出 $ sed -nf sedTestReadFile first.html This is first html script. Hello World!
写文件命令的功能之一:从一个文件中提取信息并将它放置在其他文件中。
写文件命令在被调用时就写出模式空间的内容,而不是等到到达脚本的结尾才进行写操作。
$ cat trademarkAndOS AIX Unix HP-UX Unix IRIX Unix RedHat Linux SUSE Linux Solaris Unix Ubuntu Linux #sed脚本:将内容按照操作系统类型分类 $ cat sedWritefile /Unix$/w type.unix /Linux$/w type.linux #sed指令:执行sed脚本 $ sed -f sedWritefile trademarkAndOS AIX Unix HP-UX Unix IRIX Unix RedHat Linux SUSE Linux Solaris Unix Ubuntu Linux #查看处理结果 $ cat type.unix AIX Unix HP-UX Unix IRIX Unix Solaris Unix $ cat type.linux RedHat Linux SUSE Linux Ubuntu Linux
可以在写文件命令写入文件之前对内容进行操作。
#用替换命令(s)将匹配的模式删除后再写入文件
$ cat sedWritefile_ext /Unix$/{ s/// w type.unix } /Linux$/{ s/// w type.linux } $ sed -f sedWritefile_ext trademarkAndOS AIX HP-UX IRIX RedHat SUSE Solaris Ubuntu $ cat type.unix AIX HP-UX IRIX Solaris $ cat type.linux RedHat SUSE Ubuntu
写文件编辑命令还有许多不同的应用。如,可以在脚本中使用它来生成同一个源文件的几个自定义版本。
8. 退出编辑命令
退出编辑指令(q)会使sed脚本立即退出,停止处理新的输入行。
语法:
[line-address]q
只适用于单行的地址。一旦找到与line-address匹配的行,脚本就结束运行。
注意:在将编辑操作写回到原始文件的任何程序中不要使用退出命令(q),因为在执行退出命令之后,就不会再产生输出。
在想要编辑文件的前一部分并保留剩余部分不变时,也不要使用退出命令,会产生危险后果。
$ cat trademarkAndOS AIX Unix HP-UX Unix IRIX Unix RedHat Linux SUSE Linux Solaris Unix Ubuntu Linux #打印前3行后退出 $ sed '3q' trademarkAndOS AIX Unix HP-UX Unix IRIX Unix
退出命令(q)的另一个可能的用法是在比较大的文件中提取了想要的内容后退出sed脚本。因为在sed已经找到要找的东西后继续扫描庞大的文件是相当低效的。
下面两个脚本都是打印当前目录下所有文件的前50行,但第一个脚本比第二个脚本运行的效率更高。
$ cat sedPrint1.sh #!/bin/bash #202006 for file in * do #到达第50行后退出 sed '50q' $file done $ cat sedPrint2.sh #!/bin/bash #202006 for file in * do #打印文件的前50行 sed -n '1,50p' $file done
本节结束

浙公网安备 33010602011771号