学着用Sed

原创
2013/03/26 13:42
阅读数 534


一、Sed,What?

在Gnu的介绍Sed的文档中的第一句话就是“Sed is a stream editor”,流编辑器,关于什么时流编辑器,读者可以Google一下,或者参考经典的流编辑器ed。这里提供一本学习Sed和AWK的神书 Dale Dougberty & Arnold Robbins的《Sed & AWK》。一句话概况就是“对输入流进行一系列的基本文本转换”,该句话同时概况了Sed的工作方式:读入一个输入流,然后对输入作一系列的操作,再将其输出。

二、Sed’s Usage

由于笔者的工作环境是Linux平台,因此笔者主要使用Gnu版本的Sed,但是我还未使用过Gnu对Sed扩展的部分,故此不作介绍。如果考虑跨平台,那么最好就是以Sed出世时的文章Lee E.McMahon,的“SED--A Non-interactive Text Editor”来学习。我主要以Ken Pizzini and Paolo Bonzini的《Sed--A Stream Editor》即Gnu的Sed文档来学习。

Gnu对使用Sed的描述为:

sed OPTIONS ... [SCRIPT] [INPUTFILE...]

从上面可以看到其格式符合一般Linux命令,其中SCRIPT有两种方式给出:

1)“-f sctipt_file”将sed命令放在脚步文件script_file中,然后用“-f”选项指出

2)“-e ‘command_0;command_1...’” 用”-e”选项指出由单引号包裹的命令,各命令之间可以用分号或者换行隔开。这里推荐用单引号,很多经典中都这样推荐,我也套用了,好像是说防止与shell脚本之间双引号之间的转义问题,我没有遇到过。

如果没有给出INPUTFILE或者其为“--”,那么输入文本将从标准输入中取得。INPUTFILE可以是多个文件的罗列,这样Sed会依次取出文件中的每一行,直到最后一个文件的最后一行,即遇到了EOF。

OPTIONS有多个选择,这里我根据我的使用列出几个我常使用的选项,关于选项的详细介绍可以参见《Sed--A Stream Editor》。

1)-n :根据Sed的工作方式,Sed在对一行文本做完一系列的操作后会将patter(见下面的“Sed!How to work?”)里面的内容打印到标准输出,该选项可以抑制该功能,从而只有显示的指定了“p”打印命令才会将处理结果打印出来。

2)-e : 在上面的SCRIPT部分我们已经介绍了它的使用方法了,该选项后接单引号包围的Sed命令。

3)-i [SUFFIX] :根据Sed的工作方式,Sed并不会修改源文本内容,而仅仅读入其内容,然后作处理后输出到标准输出,也可以通过重定向到某个文件。如果希望修改源文件,那么就可以使用该命令,其中可选的“SUFFIX”部分是指,如果有该部分,那么Sed在处理的过程中会把每行数据逐一的输入到“源文件名+SUFFIX”的backup文件中。Sed的工作方式是这样的,如果指定SUFFIX,就会在处理完输入流,将该backup文件重命名为源文件名,这样就没有了backup文件,感觉是仅修改了源文件;如果给出了SUFFIX选项,那么最后会将其复制一份并命名为源文件名,这样感觉就是修改源文件的同时产生了backup文件。

4)-s :在Sed使用格式里面我们说到INPUTFILE可以是多个文件的罗列,而对于流编辑器来说都有个行数的概念,比如ed中要修改某行可以通过行号定位到改行,在Sed中也可以通过行号来选择某行。默认情况下,Sed时将所有文件当成一个大的文件流的,并对他们进行统一计数,即第二个文件的首行是第一个文件末行号加一。通过该选项,可以使行号是每个文件独立的。即第一个文件的首行行号为1,第二个文件的首行行号也是1。

三、Sed!How to work?

Sed是如何处理输入流的呢?在说明这个之前首先要介绍Sed维护的两个数据区,Sed通过这两个数据区来完成对输入流的一系列操作。一个叫“pattern space”一个叫“hold space”。

我们可以用路边的停车位来举个例子,pattern我们可以认为他就是停车位前面的道路部分,他里面的数据就是Sed命令要处理的数据;hold就是停车位,其相当于一个数据的暂存区。在道路(pattern)上跑的车子可以随时进入到停车位(hold)中,而停车位(hold)中的的车子也可以随时出来到跑道(pattern)上。通常情况下我们认为车道上和停车位里面仅能容纳一辆车,即一行数据。

Sed在工作时,首先从输入流中读入一行文本,然后将文本末尾的换行符去掉后的内容放到pattern中,之后对pattern中的内容依次执行给出的Sed命令,其中的命令是逐一原地执行。比如首先执行第一条命令,然后将结果放回pattern里面,在对处理的结果执行第二条命令。这样就会导致如果第一条命令将内容改变了,那么紧接着的第二条输出命令就会输出改变后的内容,而不是原本读入时候pattern里的内容。在依次执行完了所有的命令后,Sed将pattern里面的内容取出并在末尾加上换行将其输出到标准输出(这就是默认情况下Sed会输出操作结果的原理,可以使用“-n”选项进行抑制。)。

从图中可以了解Sed工作时各个部分所做的事情,其中Hold部分后面会结合Sed命令(h、H、g、G、X)来介绍。输入/输出主要就是一个换行符的增删操作。这里重点说下Sed命令的构成。

Sed的命令有一个可选的地址或者地址范围加上一个一个字母的命令和该命令需要的选项构成,这里说的地址或者地址范围就是上面讨论的对于流编辑器特有的性质:先指定操作的位置,再进行操作。这样的步骤来选择操作范围的作用。其有多种指定方式:行号、数字推导公式、正则表达式等。而命令的组成,在《Sed & AWK》中说Sed的命令集是由25个命令组成的,见下面的Sed’s Commands。这里先来看几个命令的示例:

# 表示注释
  2 d 					# 将输入流中的第二行删除
  /[0-9]\+/p				# 打印包含数字的行的内容
  s/go/come/g		# 将输入流中每一行中的所有单词“go”替换成“come”


上面的示例分别用了行号和正则表达式来选择地址,如果没有给定地址则选择每个输入行。

如果对一个地址有多个操作,那么可以将命令罗列在“{}”中,命令之间用分号进行分割。也用换行进行分割,此时要求第一个“{”跟在命令之后,第一条命令可以与其同行或者在其后面的一行,但是结尾的“}”必须单独位于一行。

四、Sed’s Address

从上面可以知道要想对输入流进行操作,就必须先指定命令实用的地址范围,如果不指定,Sed与其他流编辑器不同的在于他会选择所有的输入行。地址的选择有多种方式,我常用的归纳有三种:行号、数字推导公式、正则表达式

1)行号: 直接用数字即可给出行号,如上面的 `2 d`中的2表示第二行,我的习惯是再行号和命令之间用一个空格隔开,这样感觉比较舒服些,不知道你怎么想。呵呵。这里要注意的就是如果在使用Sed时用了“-s”或者“-i”选项,那么行号时每个文件独立的,否则默认情况下时所有输入文件当做一个大的输入流进行计数。

2)数字推导公式: 数字推导公式也是基于行号的,因此也要注意“-s”或者“-i”选项。他通过公式来选择符合公式的行号。有两个公式:

a. first~step: 会选择行号s = first+step * n (n>=0的整数)的行

b. addr1+/- ,N:会选择行号从addr1、addr1+/-1...到addr1+/-N的行,是一个范围

3)正则表达式 :正则表达的内容这里就不赘述了,需要注意的时Gnu的和脚步语言如Python、Javascript里的书写方式有点不同(“+”用“\+”来表示,具体可以参考《Sed--A Stream Editor》或者《Sed & AWK》第三章进行详细学习。)。正则表达式的格式有两种:

a. /regexp/

b. /%regexp%

其中在第一种方式中,如果正则表达式中有“/”符,需要用“//”进行转义。而第二种方式可以减少正则表达式中包含很多/时要转义的操作。

如果在上面两种方式的后面加上大写的“I”那么在匹配时会忽略正则表达式的大小写的区别。这时和行号选择一样,我倾向于在地址和命令之间加上一个空格。如:

/[0-9]\+/ p

在数字推导里面我们已经看到了一种指定一个地址范围的方式了,就是Gnu扩展的“addr1+/- ,N”格式,其实正则表达式也可以指定多个行,如果想用行号指定多个行,则用逗号将两个行号分开即可。如:

num1,num2 # 选中从num1到num2的所有行

如果后者行号小于前者,那么仅选中前面的一个行号。

这里也可以把行号替换成正则表达式,

regexp1,regexp2

对于正则表达式则是从第一个匹配regexp1的行到最后一个匹配regexp2的所有的行。

五、Sed’s Commands

《Sed & AWK中说》Sed名集有25个命令,这里作分类进行介绍。

命令

备注

#

注释

s

替换

p    P

n    N

d    D

打印

下一个输入

删除

y ! = q

h   g

X

H  G

pattern放入hold

hold 放入 pattern

交换hold和pattern

b : t

循环

r w

读写文件

c i a

不是美国的cia,而是修改、插入、追加

1)#

如同c++里面的“//”作为行注释使用,有的实现中仅可以在脚本的开始出进行注释,但是在Gnu中是可以在任何地方进行注释的,而且,如果脚本文件的首行时是“#n”那么其相对于再调用Sed的时候使用了“-n”选项抑制自动输出功能。

2)强大的“s”命令

使用Sed的使用者,十有八九要用到该命令,甚至无论是再Gnu的文档还是最开始介绍Sed的文档中都将“s”命令单独作为一个章节来讲解,可见其重要性。

“s”命令的完整格式为:
s/regexp/replacement/flags

其中/可以替换成其他符号从而避免和正则表达式中的“\”冲突。比如可以替换成

s#regexp#replacement#flags

s!regexp!replacement!flags

但是符号必须成对。

“s”命令最基本的功能就是找到regexp匹配的部分,并用replacement替换该部分,由于替换的作用的重要性,Sed又提供了众多的flags来增强其用处。

对于repalcement,其中可以出现几个元字符。”\n”表示该处用第n(n为1到9的整数)个regexp中的\符号对包裹的正则表达式匹配的数据内容进行填充,“&”可以表示整个regexp匹配的数据内容进行填充。如:

s/\(Sed\)/Gnu \1/

将“(Sed)”转换成“Gnu (Sed)”

其flags可以将多个flag连在一起使用。

“g”:默认情况下,s只会替换第一个匹配regexp的部分,该flag会替换所有匹配的部分。

“n”: 默认情况下时第一个,g表示所有,n为一个正整数,表示第几次出现的被替换。

“p”:不等该行处理完或者遇到p命令就打印出pattern里面的内容。

“w filename”:再替换完成后,将patern内容输出到文件 filename中。

“i”:Gnu对其的扩展,使得在查找替换时不区分大小写与regexp匹配。


3)pnd

“p”表示“print”;“n”表示“next”,“d”表示“delete”

“p”命令会将pattern里面的所有内容打印到标准输出,通常该命令是和“-n”搭配使用的,抑制所有结果向标准输出仅按命令要求,输出需要输出的内容。

“n”命令将当前pattern里的内容打印到标出输出,如果使用了“-n”选项则不进行打印。同时从输入流取得下一行替换pattern中的内容,并对其执行“n”之后的命令,当某此“n”从输入流取不到下一行时,Sed忽略后面的命令并退出。比如命令

sed -n -e ‘ n; p’   data_file 

会打印偶数行的内容。

“d”命令会删除pattern里面的内容,并不管后面的命令,重新开始新一轮的从输入流中取新行放到pattern然后依次执行命令的过程,有点类似与循环中的continue。

sed -n -e ‘/[0-9]|+/d;p’

会打印出所有不包含数字的行。

至于大写PND,就要扩展我们pattern放一行数据的限制了,pattern实际上是可以放多行数据的,只是常用的情况下仅放一行数据。

“P”和“p”命令一样,只是会将pattern里面的第一个换行之前的内容都打印到标准输出。

“D”和“d”命令一样,只是将pattern里面的第一个换行之前的内容(包括换行),如果pattern里还有内容,那么对其重新执行所有命令,如果此时pattern为空那么开始新的循环。

“N”命令先向pattern里面添加一个换行符,然后从输入流中取得一个新行放入pattern中,如果取不到,那么直接退出而不管后面的命令。比如命令

sed -n -e ‘1 {N;N;N;p}’ data_file

会打印前4行内容,而命令:

sed -n -e ‘1 {N;N;N}; P; D’ data_file

会依次打印所有行的内容。

4)y!=q

为了方便,将这四个命令这样写,y不等于q。其中“!”,“=”也是两个独立的命令。

“y”命令,其格式为:

y/source-charts/dest-chars/

其中source-charts与dest-chars中的字母要一样多。Sed会将pattern里面所有出现在source-charts里面的字母替换成dest-chars里面对于顺序的字母。其中有两个典型的例子。

a.将所有字母转换成大写字母:

y/abcdefghiklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ

b.将Tab转换成空格:

y/tab/ /

“!”命令用在地址后面,然后用{}包裹后续命令,这样可以使得在地址所指范围之外的所有行都执行{}中的命令。如:

sed -n -e ‘/[0-9]\+/! {p}’ data_file

会打印所有不含数字的行。

“=”命令会打印当前输入行的行号作为单独的一行。如:

sed -n -e ‘/[0-9]\+/! {=;p}’ data_file

会打印不含数字的行的行号作为一个单独的行并跟一行其内容。

“q”,Sed遇到该命令会打印当前pattern内容,如果使用了“-n”选项则不打印,然后退出Sed而不管后面的命令,Gnu扩展还可以在“q”后面增加一个数字作为Sed的退出码。注意,该命令的地址部分只能是单行地址而不可以时地址范围,亦即不能用正则表达式。

5)h   g X H  G

这五个命令就要涉及到hold区域了,我们在举停车位的例子中将其比喻成停车位。那么怎么泊车怎么从停车位开出去呢?这五个命令就是修改pattern和hold内容的命令了。

“h”命令,将用pattern里的内容替换hold里面的内容,但是pattern里面的内容不改变。而“g”命令则执行相反的操作,用hold里面的内容替换pattern里面的内容,hold的内容不改变。

而大写的“H”命令也是望hold里面放数据,这时就不能把hold当成只能停一部车的停车位了,他可以放多个行的数据。它先想hold里面加入一个换行符,然后将pattern里面的内容追加到hold里面去。“G”执行相反的操作,想pattern里面加入一个换行符,然后将hold里面的内容追加到pattern里面。

“X”命令可以交换pattern和hold里面的内容。

6)b:t

Gnu说这桑格命令是Sed专家使用的命令,这三个命令有什么神奇的呢?这三个命令可以实现跳转,这样便可以形成条件与循环的逻辑结构,从而使Sed像一门编程语言一样,赋予Sed更多能力。

“:”命令设立一个标号。“: label” 可以设立标号label如同汇编一样。

“b”命令可以无条件跳转到特定的标号处,“b label”,使命令执行流程跳转到lable处,这个可以参考汇编里面的跳转和标号设置。

“t”命令在判断当前输入流中存在成功的替换操作后便会跳转到其后的label中,

“t  label”,当读入一个新的文件或者执行过t命令后,该条件变为否。

关于这种循环的控制,用过汇编的可能比较容易理解,Gnu说它属于高级内容,我也没有用过几次,这里就不举例了,可以参考《Sed & AWK》给出的宏替换的命令来学习。

7)cia

这里的CIA不是美国的cia,而是指修改、插入、追加。使用方法是:

c/i/a\

text

该命令后面加上“\”符,然后接text文本内容,其中a会将text内容在pattern内容输出后、下一个输入流内容读入前输出到标准输出。而i会将text内容在pattern内容输出前将text输出到标准输出。而c会删除pattern内容,然后输出text内容,并进入到下一个循环。

8)rw不是很常用,想了解可以参考《Sed & AWK》


六、Reference

[1]  Lee E.McMahon, SED--A Non-interactive Text Editor, USD

[2] Ken Pizzini and Paolo Bonzini, Sed--A Stream Editor,Gnu Document,  2009

[3] Dale Dougberty and Arnold Robbins,Sed & AWK,2nd  Edition, O’Reilly, 1997

展开阅读全文
加载中

作者的其它热门文章

打赏
0
1 收藏
分享
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部