Sed&awk笔记之sed篇

http://blog.csdn.net/a81895898/article/details/8482387正则表达式

Sed是什么

《sed and awk》一书中(1.2 A Stream Editor)是这样解释的:shell

Sed is a "non-interactive" stream-oriented editor. It is stream-oriented because, like many UNIX
programs, input flows through the program and is directed to standard output.express

Sed本质上是一个编辑器,可是它是非交互式的,这点与VIM不一样;同时它又是面向字符流的,输入的字符流通过Sed的处理后输出。这两个特性使得Sed成为命令行下面很是有用的一个处理工具。编程

若是对sed的历史有兴趣,能够看书中2.1节"Awk, by Sed and Grep, out of Ed",这里就很少介绍。vim

基本概念

sed命令的语法以下所示:缓存

sed [options] script filename

同大多数Linux命令同样,sed也是从stdin中读取输入,而且将输出写到stdout,可是当filename被指定时,则会从指定的文件中获取输入,输出能够重定向到文件中,可是须要注意的是,该文件绝对不能与输入的文件相同。bash

options是指sed的命令行参数,这一块并非重点,参数也很少。app

script是指须要对输入执行的一个或者多个操做指令(instruction),sed会依次读取输入文件的每一行到缓存中并应用script中指定的操做指令,所以而带来的变化并不会影响最初的文件(注:若是使用sed时指定-i参数则会影响最初的文件)。若是操做指令不少,为了避免影响可读性,能够将其写到文件中,并经过-f参数指定scriptfile:编程语言

sed -f scriptfile filename

这里有一个建议,在命令行中指定的操做指令最好用单引号引发来,这样能够避免shell对特殊字符的处理(如空格、$等,关于这一点能够参考简洁的Bash编程技巧续篇 - 9. 引号之间的区别)。这个建议一样适用grep/awk等命令,固然若是有时候确实不适合使用单引号时,记得对特殊字符转义。编辑器

不管是将操做指令经过命令行指定,仍是写入到文件中做为一个sed脚本,必须包含至少一个指令,不然用sed就没有意义了。通常会同时指定多个操做指令,这时候指令之间的顺序就显得很是重要。而你的脑海中必须有这么一个概念,即每一个指令应用后,当前输入的行会变成什么样子。要作到这一点首先必需要了解sed的工做原理,要作到“知其然,且知其因此然”。

每条操做指令由pattern和procedure两部分组成,pattern通常是用'/'分隔的正则表达式(在sed中也有多是行号,具体参见Sed命令地址匹配问题总结),而procedure则是一连串编辑命令(action)。

sed的处理流程,简化后是这样的:

  1. 读入新的一行内容到缓存空间;
  2. 从指定的操做指令中取出第一条指令,判断是否匹配pattern;
  3. 若是不匹配,则忽略后续的编辑命令,回到第2步继续取出下一条指令;
  4. 若是匹配,则针对缓存的行执行后续的编辑命令;完成后,回到第2步继续取出下一条指令;
  5. 当全部指令都应用以后,输出缓存行的内容;回到第1步继续读入下一行内容;
  6. 当全部行都处理完以后,结束;

具体流程见下图:
simple sed process flow chart

简单例子

概念若是脱离实际的例子就会显得很是枯燥无趣,这本书在这一点上处理得很好,都是配上实际的例子来说解的。不过有点遗憾的是,书中的例子大可能是与troff macro有关的,咱们很难有条件来实际测试例子,在笔记中我会尽可能使用更加浅显易懂的例子来讲明。

下面是摘自书中的一个例子,假设有个文件list:

John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

这个例子中用到的知识点都会在后文中介绍,你能够先知道有这个东西就行。

假如,如今打算将MA替换成Massachusetts,可使用替换命令s:

$ sed -e 's/MA/Massachusetts/' list John Daggett, 341 King Road, Plymouth Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston Massachusetts

这里面的-e选项是可选的,这个参数只是在命令行中同时指定多个操做指令时才须要用到,如:

$ sed -e 's/ MA/, Massachusetts/' -e 's/ PA/, Pennsylvania/' list John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls, Pennsylvania Eric Adams, 20 Post Road, Sudbury, Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston, Massachusetts

即便在多个操做指令的状况下,-e参数也不是必需的,我通常不会加-e参数,好比上面的例子能够换成下面的写法:

$ sed 's/ MA/, Massachusetts/;s/ PA/, Pennsylvania/' list 

操做指令之间能够用逗号分隔,这点和shell命令能够用逗号分隔是同样的。

这里提早说一点使用s替换命令须要当心的地方,不要忘记结尾的斜杠,若是你遇到下面的错误说明你犯了这个错误:

$ sed -e 's/ MA/, Massachusetts' list sed: -e expression #1, char 21: unterminated `s' command

假如这时,我只想输出替换命令影响的行,而不想输出文件的全部内容,须要怎么办?这个时候能够利用替换命令s的p选项,它会将替换后的内容打印到标准输出。可是仅仅这样是不够的,不然输出的结果并不是咱们所想要的:

$ sed -e 's/ MA/, Massachusetts/p' list John Daggett, 341 King Road, Plymouth, Massachusetts John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury, Massachusetts Eric Adams, 20 Post Road, Sudbury, Massachusetts Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston, Massachusetts Sal Carpenter, 73 6th Street, Boston, Massachusetts

从上面能够看出,文件的全部行都被打印到标准输出,而且被替换命令修改的行输出了两遍。形成这个问题的缘由是,sed命令应用完全部指定以后默认会将当前行打印输出,因此就有了上面的结果。解决方法是在使用sed命令是指定-n参数,该参数会抑制sed默认的输出:

$ sed -n 's/ MA/, Massachusetts/p' list John Daggett, 341 King Road, Plymouth, Massachusetts Eric Adams, 20 Post Road, Sudbury, Massachusetts Sal Carpenter, 73 6th Street, Boston, Massachusetts

正则表达式

书中的第3章的内容都是介绍正则表达式,这是由于sed的不少地方都须要用到正则表达式,好比操做指令的pattern部分以及替换命令中用到的匹配部分。关于正则这部分能够看Linux/Unix工具与正则表达式的POSIX规范,文章中已经讲得很是清楚了,我自已在忘记一些内容的时候也是去查阅这篇文章。

 

sed并不是是将一个编辑命令分别应用到每一行,而后再取下一个编辑命令。偏偏相反,sed是以行的方式来处理的。另一方面,每一行都是被读入到一块缓存空间,该空间名为模式空间(pattern space),这是一个很重要的概念,在后文中会屡次被说起。所以sed操做的都是最初行的拷贝,同时后续的编辑命令都是应用到前面的命令编辑后输出的结果,因此编辑命令之间的顺序就显得格外重要。

简单例子

让咱们来看一个很是简单的例子,将一段文本中的pig替换成cow,而且将cow替换成horse:

$ sed 's/pig/cow/;s/cow/hores/' input

初看起来好像没有问题,可是其实是错误的,缘由是第一个替换命令将全部的pig都替换成cow,紧接着的替换命令是基于前一个结果处理的,将全部的cow都替换成horse,形成的结果是所有的pig/cow都被替换成了horse,这样违背了咱们的初衷。

在这种状况下,只须要调换下两个编辑命令的顺序:

$ sed 's/cow/hores/;s/pig/cow/' input

模式空间的转换

sed只会缓存一行的内容在模式空间,这样的好处是sed能够处理大文件而不会有任何问题,不像一些编辑器由于要一次性载入文件的一大块内容到缓存中而致使内存不足。下面用一个简单的例子来说解模式空间的转换过程,以下图所示:

模式空间的转换

如今要把一段文本中的Unix System与UNIX System都要统一替换成The UNIX Operating System,所以咱们用两句替换命令来完成这个目的:

s/Unix /UNIX / s/UNIX System/UNIX Operating System/

对应上图,过程以下:

  1. 首先一行内容The Unix System被读入模式空间;
  2. 应用第一条替换命令将Unix替换成UNIX;
  3. 如今模式空间的内容变成The UNIX System;
  4. 应用第二条替换命令将UNIX System替换成UNIX Operating System;
  5. 如今模式空间的内容变成The UNIX Operating System;
  6. 全部编辑命令执行完毕,默认输出模式空间中的行;

地址匹配

地址匹配在sed中是很是重要的一块内容,由于它限制sed的编辑命令到底应用在哪些行上。默认状况下,sed是全局匹配的,即对全部输入行都应用指定的编辑命令,这是由于sed依次读入每一行,每一行都会成为当前行并被处理,因此s/CA/California/g会将全部输入行的CA替换成California。这一点跟vi/vim是不同的,众所周知,vim的替换命令默认是替换当前行的内容,除非你指定%s才会做全局替换。

能够经过指定地址来限制sed的处理范围,例如只想将替换包含Sebastopol的行:

Sebastopol/s/CA/California/

/Sebastopol/是一个正则表达式匹配包含Sebastopol的行,所以像行“San Francisco, CA”则不会被替换。

sed命令中能够包含0个、1个或者2个地址(地址对),地址能够为正则表达式(如/Sebastopol/),行号或者特殊的行符号(如$表示最后一行):

  • 若是没有指定地址,默认将编辑命令应用到全部行;
  • 若是指定一个地址,只将编辑命令应用到匹配该地址的行;
  • 若是指定一个地址对(addr1,addr2),则将编辑命令应用到地址对中的全部行(包括起始和结束);
  • 若是地址后面有一个感叹号(!),则将编辑命令应用到不匹配该地址的全部行;

为了方便理解上述内容,咱们以删除命令(d)为例,默认不指定地址将会删除全部行:

$ sed 'd' list $ 

指定地址则删除匹配的行,如删除第一行:

$ sed '1d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

或者删除最后一行,$符号在这里表示最后一行,这点要下正则表达式中的含义区别开来:

$ sed '$d' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA

这里经过指定行号删除,行号是sed命令内部维护的一个计数变量,该变量只有一个,而且在多个文件输入的状况下也不会被重置。

前面都是以行号来指定地址,也能够经过正则表达式来指定地址,如删除包含MA的行:

$ sed '/MA/d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA

经过指定地址对能够删除该范围内的全部行,例如删除第3行到最后一行:

$ sed '2,$d' list John Daggett, 341 King Road, Plymouth MA

使用正则匹配,删除从包含Alice的行开始到包含Hubert的行结束的全部行:

$ sed '/Alice/,/Hubert/d' list John Daggett, 341 King Road, Plymouth MA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

固然,行号和地址对是能够混用的:

$ sed '2,/Hubert/d' list John Daggett, 341 King Road, Plymouth MA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

若是在地址后面指定感叹号(!),则会将命令应用到不匹配该地址的行:

$ sed '2,/Hubert/!d' list Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA

以上介绍的都是最基本的地址匹配形式,GNU Sed(也是咱们用的sed版本)基于此添加了几个扩展的形式,具体能够看man手册,或者能够看我以前写的Sed命令地址匹配问题总结一文。

上面说的内容都是对匹配的地址执行单个命令,若是要执行多个编辑命令要怎么办?sed中能够用{}来组合命令,就比如编程语言中的语句块,例如:

$ sed -n '1,4{s/ MA/, Massachusetts/;s/ PA/, Pennsylvania/;p}' list John Daggett, 341 King Road, Plymouth, Massachusetts Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls, Pennsylvania

 

在开始以前,首先回顾上一篇的重点内容:地址匹配。上一篇中介绍过,地址能够指定0个,1个或者2个。地址的形式能够为斜杠分隔的正则表达式(例如/test/),行号(例如3,5)或者特殊符号(例如$)。若是没有指定地址,说明sed应用的编辑命令是全局的;若是是1个地址,编辑命令只是应用到匹配的那一行;若是是一对地址,编辑命令则应用到该地址对匹配的行范围。关于地址匹配的内容具体能够看Sed命令地址匹配问题总结

书中说,对于sed编辑命令的语法有两种约定,分别是

[address]command               # 第一种
[line-address]command          # 第二种

第一种[address]是指能够为任意地址包括地址对,第二种[line-address]是指只能为单个地址。文中指出,例如i(后方插入命令)、a(前方追加命令)以及=(打印行号命令)等都属于第二种命令,可是实际上我在ArchLinux上测试,指定地址对也没有报错。不过为了兼容性,建议按做者所说的作。

如下是要介绍的所有基础命令:

名称 命令 语法 说明
替换 s [address]s/pattern/replacement/flags 替换匹配的内容
删除 d [address]d 删除匹配的行
插入 i [line-address]i\

text
在匹配行的前方插入文本
追加 a [line-address]a\

text
在匹配行的后方插入文本
行替换 c [address]c\

text
将匹配的行替换成文本text
打印行 p [address]p 打印在模式空间中的行
打印行号 = [address]= 打印当前行行号
打印行 l [address]l 打印在模式空间中的行,同时显示控制字符
转换字符 y [address]y/SET1/SET2/ 将SET1中出现的字符替换成SET2中对应位置的字符
读取下一行 n [address]n 将下一行的内容读取到模式空间
读文件 r [line-address]r file 将指定的文件读取到匹配行以后
写文件 w [address]w file 将匹配地址的全部行输出到指定的文件中
退出 q [line-address]q 读取到匹配的行以后即退出

替换命令: s

替换命令的语法是:

[address]s/pattern/replacement/flags

其中[address]是指地址,pattern是替换命令的匹配表达式,replacement则是对应的替换内容,flags是指替换的标志位,它能够包含如下一个或者多个值:

  • n: 一个数字(取值范围1-512),代表仅替换前n个被pattern匹配的内容;
  • g: 表示全局替换,替换全部被pattern匹配的内容;
  • p: 仅当行被pattern匹配时,打印模式空间的内容;
  • w file:仅当行被pattern匹配时,将模式空间的内容输出到文件file中;

若是flags为空,则默认替换第一次匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/' column1;column2 column3 column4

若是flags中包含g,则表示全局匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/g' column1;column2;column3;column4

若是flags中明确指定替换第n次的匹配,例如n=2:

$ echo "column1 column2 column3 column4" | sed 's/ /;/2' column1 column2;column3 column4

当替换命令的pattern与地址部分是同样的时候,好比/regexp/s/regexp/replacement/能够省略替换命令中的pattern部分,这在单个编辑命令的状况下没多大用处,可是在组合命令的场景下仍是能省很多功夫的,例以下面是摘自文中的一个段落:

The substitute command is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
pattern is specified, the substitute command matches what is matched by the
address.  This can be useful when the substitute command is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

如今要在substitute command后面增长("s"),同时在被修改的行前面增长+号,如下是使用的sed命令:

$ sed '/substitute command/{s//&("s")/;s/^/+ /}' paragraph.txt 

这里咱们用到了组合命令,而且地址匹配的部分和第一个替换命令的匹配部分是同样的,因此后者咱们省略了,在replacement部分用到了&这个元字符,它表明以前匹配的内容,这点咱们在后面介绍。执行后的结果为:

+ The substitute command("s") is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
+ pattern is specified, the substitute command("s") matches what is matched by the
+ address.  This can be useful when the substitute command("s") is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

替换命令的一个技巧是中间的分隔符是能够更改的(这个技巧咱们在简洁的Bash编程技巧续篇 - 5. 你知道sed的这个特性吗?中也曾经介绍过),这个技巧在有些地方很是有用,好比路径替换,下面是采用默认的分隔符和使用感叹号做为分隔符的对比:

$ find /usr/local -maxdepth 2 -type d | sed 's/\/usr\/local\/man/\/usr\/share\/man/' $ find /usr/local -maxdepth 2 -type d | sed 's!/usr/local/man!/usr/share/man!'

替换命令中还有一个很重要的部分——replacement(替换内容),即将匹配的部分替换成replacement。在replacemnt部分中也有几个特殊的元字符,它们分别是:

  • &: 被pattern匹配的内容;
  • \num: 被pattern匹配的第num个分组(正则表达式中的概念,\(..\)括起来的部分称为分组;
  • \: 转义符号,用来转义&,\, 回车等符号

&元字符咱们已经在上面的例子中介绍过,这里举个例子介绍下\num的用法。在Linux系统中,默认都开了几个tty可让你切换(ctrl+alt+1, ctrl+alt+2 ...),例如CentOS 6.0+的系统默认是6个:

[root@localhost ~]# grep -B 1 ACTIVE_CONSOLES /etc/sysconfig/init  # What ttys should gettys be started on? ACTIVE_CONSOLES=/dev/tty[1-6]

能够经过修改上面的配置来减小开机启动的时候建立的tty个数,好比咱们只想要2个:

[root@localhost ~]# sed -r -i 's!(ACTIVE_CONSOLES=/dev/tty\[1-)6]!\12]!' /etc/sysconfig/init  ACTIVE_CONSOLES=/dev/tty[1-2]

其中-i参数表示直接修改原文件,-r参数是指使用扩展的正则表达式(ERE),扩展的正则表达式中分组的括号不须要用反斜杠转义"(ACTIVE_CONSOLES=/dev/tty\[1-)",这里[是有特殊含义的(表示字符组),因此须要转义。在替换的内容中使用\1来引用这个匹配的分组内容,1表明分组的编号,表示第一个分组。

删除命令: d

删除命令的语法是:

[address]d

删除命令能够用于删除多行内容,例如1,3d会删除1到3行。删除命令会将模式空间中的内容所有删除,而且致使后续命令不会执行而且读入新行,由于当前模式空间的内容已经为空。咱们能够试试:

$ sed '2,${d;=}' list John Daggett, 341 King Road, Plymouth MA

以上命令尝试在删除第2行到最后一行以后,打印当前行号(=),可是事实上并无执行该命令。

插入行/追加行/替换行命令: i/a/c

这三个命令的语法以下所示:

# Append 追加
[line-address]a\
text
# Insert 插入
line-address]i\
text
# Change 行替换 
[address]c\
text

以上三个命令,行替换命令(c)容许地址为多个地址,其他两个都只容许单个地址(注:在ArchLinux上测试代表,追加和插入命令都容许多个地址,sed版本为GNU sed version 4.2.1

追加命令是指在匹配的行后面插入文本text;相反地,插入命令是指匹配的行前面插入文本text;最后,行替换命令会将匹配的行替换成文本text。文本text并无被添加到模式空间,而是直接输出到屏幕,所以后续的命令也不会应用到添加的文本上。注意,即便使用-n参数也没法抑制添加的文本的输出。

咱们用实际的例子来简单介绍下这三个命令的用法,依然用最初的文本list:

$ cat list
John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

如今,咱们要在第2行后面添加'------':

$ sed '2a\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA -------------------------------------- Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

或者能够在第3行以前插入:

$ sed '3i\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA -------------------------------------- Orville Thomas, 11345 Oak Bridge Road, Tulsa OK Terry Kalkas, 402 Lans Road, Beaver Falls PA Eric Adams, 20 Post Road, Sudbury MA Hubert Sims, 328A Brook Road, Roanoke VA Amy Wilde, 334 Bayshore Pkwy, Mountain View CA Sal Carpenter, 73 6th Street, Boston MA

咱们来测试下文本是否确实没有添加到模式空间,由于模式空间中的内容默认是会打印到屏幕的:

$ sed -n '2a\ -------------------------------------- ' list --------------------------------------

经过-n参数来抑制输出后发现插入的内容依然被输出,因此能够断定插入的内容没有被添加到模式空间。

使用行替换命令将第2行到最后一行的内容所有替换成'----':

$ sed '2,$c\ -------------------------------------- ' list John Daggett, 341 King Road, Plymouth MA --------------------------------------

打印命令: p/l/=

这里纯粹的打印命令应该是指p,可是由于后二者(l和=)和p差很少,而且相对都比较简单,因此这里放到一块儿介绍。

这三个命令的语法是:

[address]p
[address]=
[address]l

p命令用于打印模式空间的内容,例如打印list文件的第一行:

$ sed -n '1p' list John Daggett, 341 King Road, Plymouth MA

l命令相似p命令,不过会显示控制字符,这个命令和vim的list命令类似,例如:

$ echo "column1 column2 column3^M" | sed -n 'l' column1\tcolumn2\tcolumn3\r$

=命令显示当前行行号,例如:

$ sed '=' list 1 John Daggett, 341 King Road, Plymouth MA 2 Alice Ford, 22 East Broadway, Richmond VA 3 Orville Thomas, 11345 Oak Bridge Road, Tulsa OK 4 Terry Kalkas, 402 Lans Road, Beaver Falls PA 5 Eric Adams, 20 Post Road, Sudbury MA 6 Hubert Sims, 328A Brook Road, Roanoke VA 7 Amy Wilde, 334 Bayshore Pkwy, Mountain View CA 8 Sal Carpenter, 73 6th Street, Boston MA

转换命令: y

转换命令的语法是:

[address]y/SET1/SET2/

它的做用是在匹配的行上,将SET1中出现的字符替换成SET2中对应位置的字符,例如1,3y/abc/xyz/会将1到3行中出现的a替换成x,b替换成y,c替换成z。是否是以为这个功能很熟悉,其实这一点和tr命令是同样的。能够经过y命令将小写字符替换成大写字符,不过命令比较长:

$ echo "hello, world" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' HELLO, WORLD

使用tr命令来转换成大写:

$ echo "hello, world" | tr a-z A-Z HELLO, WORLD

取下一行命令: n

取下一行命令的语法为:

[address]n

n命令为将下一行的内容提早读入,而且将以前读入的行(在模式空间中的行)输出到屏幕,而后后续的命令会应用到新读入的行上。所以n命令也会同d命令同样改变sed的控制流程。

书中给出了一个例子来介绍n的用法,假设有这么一个文本:

$ cat text
.H1 "On Egypt" Napoleon, pointing to the Pyramids, said to his troops: "Soldiers, forty centuries have their eyes upon you."

如今要将.H1后面的空行删除:

$ sed '/.H1/{n;/^$/d}' text .H1 "On Egypt" Napoleon, pointing to the Pyramids, said to his troops: "Soldiers, forty centuries have their eyes upon you."

读写文件命令: r/w

读写文件命令的语法是:

[line-address]r file
[address]w file

读命令将指定的文件读取到匹配行以后,而且输出到屏幕,这点相似追加命令(a)。咱们以书中的例子来说解读文件命令。假设有一个文件text:

$ cat text
For service, contact any of the following companies: [Company-list] Thank you.

同时咱们有一个包含公司名称列表的文件company.list:

$ cat company.list Allied Mayflower United

如今咱们要将company.list的内容读取到[Company-list]以后:

$ sed '/^\[Company-list]/r company.list' text For service, contact any of the following companies: [Company-list] Allied Mayflower United Thank you.

更好的处理应当是用公司名称命令替换[Company-list],所以咱们还须要删除[Company-list]这一行:

$ sed '/^\[Company-list]/r company.list;/^\[Company-list]/d;' text For service, contact any of the following companies: [Company-list] Thank you.

可是结果咱们非但没有删除[Company-list],并且company.list的内容也不见了?这是怎么回事呢?

下面是我猜想的过程,读文件的命令会将r空格后面的全部内容都当成文件名,即company.list;/^\[Company-list]/d;,而后读取命令的时候发现该文件不存在,可是sed命令读取不存在的文件是不会报错的。因此什么事都没干成。

咱们用-e选项将两个命令分开就正常了:

$ sed -e '/^\[Company-list]/r company.list' -e '/^\[Company-list]/d;' text For service, contact any of the following companies: Allied Mayflower United Thank you.

写命令将匹配地址的全部行输出到指定的文件中。假设有一个文件内容以下,前半部分是人名,后半部分是区域名称:

$ cat text 
Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South

如今咱们要将不一样区域的人名字写到不一样的文件中:

$ sed '/Northeast$/w region.northeast > /South$/w region.south > /Midwest$/w region.midwest > /West$/w region.west' text Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South

查看输出的文件:

$ cat region. region.midwest region.northeast region.south region.west $ cat region.midwest Dennis, Jim Midwest Madison, Sylvia Midwest

咱们也能够试试将全部的w命令放到一块儿用分号分隔,发现会出下面的错误,它把w空格后面的部分看成文件名,这样就验证了上面的猜想:

$ sed '/Northeast$/w region.northeast;/south$/w region.south;' text sed: couldn't open file region.northeast;/south$/w region.south;: No such file or directory

退出命令: q

退出命令的语法是

[line-address]q

当sed读取到匹配的行以后即退出,不会再读入新的行,而且将当前模式空间的内容输出到屏幕。例如打印前3行内容:

$ sed '3q' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

打印前3行也能够用p命令:

$ sed -n '3p' list John Daggett, 341 King Road, Plymouth MA Alice Ford, 22 East Broadway, Richmond VA Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

可是对于大文件来讲,前者比后者效率更高,由于前者读取到第N行以后就退出了。后者虽然打印了前N行,可是后续的行仍是要继续读入,只不会不做处理。

到此为止,sed基础命令的部分就介绍完了。下面一篇的内容会在最近几天更新上来,主要会介绍sed高级命令。不过通常状况下,会使用基础命令已经差很少了,高级命令不必定须要去了解。

下面是我整理的一份思惟导图(附.xmind文件下载地址):
sed基础命令

上一篇中介绍的基础命令都是面向行的,通常状况下,这种处理并无什么问题,可是当匹配的内容是错开在两行时就会有问题,最明显的例子就是某些英文单词会被分红两行。

幸运地是,sed容许将多行内容读取到模式空间,这样你就能够匹配跨越多行的内容。本篇笔记主要介绍这些命令,它们可以建立多行模式空间而且处理之。其中,N/D/P这三个多行命令分别对应于小写的n/d/p命令,后者咱们在上一篇已经介绍。它们的功能是相似的,区别在于命令影响的内容不一样。例如D命令与d命令一样是删除模式空间的内容,只不过d命令会删除模式空间中全部的内容,而D命令仅会删除模式空间中的第一行。

读下一行:N

N命令将下一行的内容读取到当前模式空间,可是下n命令不同的地方是N命令并无直接输出当前模式空间中的行,而是把下一行追加到当前模式空间,两行之间用回车符\n链接,以下图所示:

模式空间包含多行以后,正则表达式的^/$符号的意思就变了,^是匹配模式空间的最开始而非行首,$是匹配模式空间的最后位置而非行尾。

书中给的第一例子,是替换如下文本中的"Owner and Operator Guide"为"Installation Guide",正如咱们在本篇开头说的Owner and Operator Guide跨越在两行:

Consult Section 3.1 in the Owner and Operator
Guide for a description of the tape drives
available on your system.

咱们用N命令试下手:

$ sed '/Operator$/{N;s/Owner and Operator\nGuide/Installation Guide/}' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system.

不过这个例子有两个局限:

  • 咱们知道Owner and Operator Guide分割的位置;
  • 执行替换命令后,先后两行拼接在一块儿,致使这行过长;

第二点,能够这样解决:

$ sed '/Operator$/{N;s/Owner and Operator\nGuide /Installation Guide\n/}' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system.

如今去掉第1个前提,咱们引入更加复杂的测试文本,这段文本中Owner and Operator Guide有位于一行的,也有跨越多行的状况:

$ cat text
Consult Section 3.1 in the Owner and Operator Guide for a description of the tape drives available on your system. Look in the Owner and Operator Guide shipped with your system. Two manuals are provided including the Owner and Operator Guide and the User Guide. The Owner and Operator Guide is shipped with your system.

相应地修改下以前执行的命令,以下所示:

$ sed 's/Owner and Operator Guide/Installation Guide/ /Owner/{ N s/ *\n/ / s/Owner and Operator Guide */Installation Guide\ / }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Installation Guide is shipped with your system.

这里咱们首先将在单行出现的Owner and Operator Guide替换为Installation Guide,而后再寻找匹配Owner的行,匹配后读取下一行的内容到模式空间,而且将中间的换行符替换成空格,最后再替换Owner and Operator Guide。

解释起来很简单,可是中间仍是有些门道的。好比你可能以为这里最前面的s/Owner and Operator Guide/Installation Guide/命令是多余的,假设你删除这一句:

$ sed '/Owner/{ > N > s/ *\n/ / > s/Owner and Operator Guide */Installation Guide\ > / > }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Owner and Operator Guide is shipped with your system.

最明显的问题是最后一行没有被替换,缘由是当最后一行的被读入到模式空间后,匹配Owner,执行N命令读入下一行,可是由于当前已是最后一行,因此N读取替换,告诉sed能够退出了,sed也不会继续执行接下来的替换命令(注:书中说最后一行不会被打印输出,我这边测试是会输出的)。因此这里须要在使用N的时候加一个判断,即当前行为最后一行时,不读取下一行的内容:$!N,这一点在不少场合都是有用的,更改后从新执行:

$ sed '/Owner/{ > $!N > s/ *\n/ / > s/Owner and Operator Guide */Installation Guide\ > / > }' text Consult Section 3.1 in the Installation Guide for a description of the tape drives available on your system. Look in the Installation Guide shipped with your system. Two manuals are provided including the Installation Guide and the User Guide. The Installation Guide is shipped with your system.

上面只是为了用例子说明N的用法,可能解决方案未必是最好的。

删除行:D

该命令删除模式空间中第一行的内容,而它对应的小d命令删除模式空间的全部内容。D不会致使读入新行,相反它会回到最初的编辑命令,重要应用在模式空间剩余的内容上。

后面半句开始比较难以理解,用书中的一个例子来解释下。如今咱们有一个文本文件,内容以下所示,行之间有空行:

$ cat text
This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

如今咱们要删除多余的空行,将多个空行缩减成一行。假如咱们使用d命令来删除,很简单的逻辑:

$ sed '/^$/{N;/^\n$/d}' text This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

咱们会发现一个奇怪的结果,奇数个数的相连空行已经被合并成一行,可是偶数个数的却所有被删除了。形成这样的缘由须要从新翻译下上面的命令,当匹配一个空行是,将下一行也读取到模式空间,而后若下一行也是空行,则模式空间中的内容应该是\n,所以匹配^\n$,从而执行d命令会将模式空间中的内容清空,结果就是相连的两个空行都被删除。这样就能够理解为何相连奇数个空行的状况下是正常的,而偶数个数就有问题了。

这种状况下,咱们就应该用D命令来处理,这样作就获得预期的结果了:

$ sed '/^$/{N;/^\n$/D}' text This line is followed by 1 blank line. This line is followed by 2 blank lines. This line is followed by 3 blank lines. This line is followed by 4 blank lines. This is the end.

D命令只会删除模式空间的第一行,并且删除后会从新在模式空间的内容上执行编辑命令,相似造成一个循环,前提是相连的都是空行。当匹配一个空行时,N读取下一行内容,此时匹配^\n$致使模式空间中的第一行被删除。如今模式空间中的内容是空的,从新执行编辑命令,此时匹配/^$/。继续读取下一行,当下一行依然为空行时,重复以前的动做,不然输出当前模式空间的内容。形成的结果是连续多个空行,只有最后一个空行是保留输出的,其他的都被删除了。这样的结果才是咱们最初但愿获得的。

打印行:P

P命令与p命令同样是打印模式空间的内容,不一样的是前者仅打印模式空间的第一行内容,然后者是打印全部的内容。由于编辑命令所有执行完以后,sed默认会输出模式空间的内容,因此通常状况下,p和P命令都是与-n选项一块儿使用的。可是有一种状况是例外的,即编辑命令的执行流程被改变的状况,例如N,D等。不少状况下,P命令都是用在N命令以后,D命令以前的。这三个命令合起来,能够造成一人输入输出的循环,而且每次只打印一行:读入一行后,N继续读下一行,P命令打印第一行,D命令删除第一行,执行流程回到最开始重复该过程。

$ echo -e "line1\nline2\nline3" | sed '$!N;P;D'

不过多行命令用起来要格外当心,你要用本身的大脑去演算一遍执行的过程,要否则很容易出错,好比:

$ echo -e "line1\nline2\nline3" | sed -n 'N;1P'

你可能指望打印第一行的内容,事实上并无输出。缘由是当N继续读入第二行后,当前行号已是2了,咱们在第二篇笔记中曾经说过,行号只是sed在内部维护的一个计数变量而已,每当读入新的一行,行号就加一:

$ echo -e "line1\nline2\nline3" | sed -n '$!N;=' 2 3

咱们依然用替换Unix System为Unix Operating System做为例子,介绍N/P/D三个命令是如何配合使用的。

示例文本以下所示,为了举例方便,这段文本仅有三行内容,恰好能够演示一个循环的处理过程:

$ cat text
The UNIX System and UNIX ...

执行的命令:

$ sed '/UNIX$/{ > N > s/\nSystem/ Operating &/ > P > D > }' text The UNIX Operating System and UNIX ...

执行过程以下图所示:
NDP Loop

以上三个命令的用法与以前介绍的基础命令是大相径庭的,有些同窗可能都没有接触过。在下一篇中,我会介绍更多高级的命令,同时为引入一个新的概念:保持空间(Hold Space)。

保持空间

保持空间用于保存模式空间的内容,模式空间的内容能够复制到保持空间,一样地保持空间的内容能够复制回模式空间。sed提供了几组命令用来完成复制的工做,其它命令没法匹配也不能修改模式空间的内容。

操做保持空间的命令以下所示:

名称 命令 说明
保存(Hold) h/H 将模式空间的内容复制或者追加到保持空间
取回(Get) g/G 将保持空间的内容复制或者追加到模式空间
交换(Exchange) x 交换模式空间和保持空间的内容

这几组命令提供了保存、取回以及交换三个动做,交换命令比较容易理解,保存命令和取回命令都有大写和小写两种形式,这两种形式的区别是小写的是将会覆盖目的空间的内容,而大写的是将内容追加到目的空间,追加的内容和原有的内容是以\n分隔。

基本使用

咱们随便试试这几个命令,假设有以下测试文本:

$ cat text
1 2 11 22 111 222

1. 首先,仅使用h/H或者g/G命令:

使用h命令:

$ sed 'h' text 1 2 11 22 111 222

使用G命令:

$ sed 'G' text 1 2 11 22 111 222

前者返回的结果正常,由于复制到保持空间的内容并无取回;后者每一行的后面都多了一个空行,缘由是每行都会从保持空间取回一行,追加(大写的G)到模式空间的内容以后,以\n分隔。

2. 使用x命令交换空间

$ sed 'x' text 1 2 11 22 111

命令执行后,发现前面多了一个空行而且最后一行不见了。我在前面一直强调sed命令用好,要有用大脑回顾命令执行过程的能力:

* 当读入第一行的时候,模式空间中的内容是第一行的内容,而保持空间是空的,这个时候交换两个空间,致使模式空间为空,保持空间为第一行的内容,所以输出为空行;
* 当读入下一行以后,模式空间为第2行的内容,保持空间为第一行的内容,交换后输出第1行的内容;
* 依次读入每一行,输出上一行的内容;
* 直到最后一行被读入到模式空间,交换后输出倒数第二行的内容,而最后一行的内容并无输出,此时命令执行结束。

深刻使用

上面的例子简单地介绍了保持空间命令的基本使用方法,这些命令单个使用可能效果不大,可是组合起来的效果是很是好的。

1.第一个例子: 使用逗号拼接行

$ sed 'H;$!d;${x;s/^\n//;s/\n/,/g}' text 1,11,2,11,22,111,222

上面的命令执行过程是这样的,使用H将每一行都追加到保持空间,这里利用d命令打断常规的命令执行流程,让sed继续读入新的一行,直接到将最后一行都放到保持空间。这个时候使用x命令将保持空间的内容交换到模式空间,模式空间的内容如今是这样的:\n1\n11\n2\n11\n22\n111\n222。替换的步骤分红两个,首先去掉首个回车符,而后把剩余的回车符替换成逗号。

其实上面的过程能够包装成一个经常使用的函数:

$ function join_lines() > { > sed 'H;$!d;${x;s/^\n//;s/\n/,/g}' $1 > } $ join_lines text 1,11,2,11,22,111,222

进一步,咱们可让分隔符能够经过参数设置,另一方面删除文件名的参数,而改为常见的过滤器命令形式(即经过管道传递输入):

$ function join_lines() > { > local delim=${1:-,} > sed 'H;$!d;${x;s/^\n//;s/\n/'$delim'/g}' > } $ cat text | join_lines ';' 1;11;2;11;22;111;222

可是若是咱们要用&做为符号,就会出现问题:

$ cat text | join_lines '&' 1 11 2 11 22 111 222

上面并无&做为分隔符,这是由于&是元字符,表示匹配的部分,这里恰好是回车符\n。所以咱们须要对分隔符进行转义:

$ function join_lines() > { > local delim=${1:-,} > sed 'H;$!d;${x;s/^\n//;s/\n/\'$delim'/g}' > } $ cat text | join_lines '&' 1&11&2&11&22&111&222

2.第二个例子:将语句中的特定单词转换成大写

如今有这样的文本,有许多相似这样的find the Match statement语句,其中Match是语句的名称,可是这个单词的大小写不统一,有些地方是小写的,有些地方是首字符大写,如今咱们要作的是把这个单词统一转换成大写。

容易联想到的是Sed&awk笔记之sed篇:基础命令中介绍的y命令,利用y命令确实能够作到小写转换成大写,转换的命令是这样的:

y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

可是y命令是会把模式空间的全部内容都转换,这样不能知足咱们的需求。可是咱们能够利用保持空间保存当前行,而后处理模式空间中的内容:

/the .* statement/{ h s/.*the \(.*\) statement.*/\1/ y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ G s/\(.*\)\n\(.*the \).*\( statement.*\)/\2\1\3/ }

老规矩一条一条过上面的命令,为了方便说明,每一个命令解释后我都会给出当前模式空间和保持空间的内容。

首先找到须要处理的行(假设当前行为find the Match statement)。

Pattern space: find the Match statement

将当前行保存到保持空间:

Pattern space: find the Match statement
Hold space: find the Match statement

而后利用替换命令获取须要处理的单词:

Pattern space: Match
Hold space: find the Match statement

而后经过转换命令将其转换成大写:

Pattern space: MATCH
Hold space: find the Match statement

如今再利用G命令将保持空间的内容追加到模式空间最后:

Pattern space: MATCH\nfind the Match statement
Hold space: find the Match statement

最后再次利用替换命令处理下:

Pattern space: find the MATCH statement
Hold space: find the Match statement

流程控制

通常状况下,sed是将编辑命令从上到下依次应用到读入的行上,可是像d/n/D/N命令都可以在必定程度上改变默认的执行流程,甚至利用N/D/P三个命令能够造成一个强大的循环处理流程。除此以外,其实sed还提供了分支命令(b)和测试(test)两个命令来控制流程,这两个命令能够跳转到指定的标签(label)位置继续执行命令。标签是以冒号开头的标记,以下例中的:top标签:

:top command1 command2 /pattern/b top command3

当执行到/pattern/b top时,若是匹配pattern,则跳转到:top标签所在的位置,继续执行下一个命令command1。

若是没有指定标签,则将控制转移到脚本的结尾处。也许这只是一个默认的行为,可是有时候若是用得好也是很是有用的,例如:

/pattern/b command 1 command 2 command 3

当执行到/pattern/b时,若是匹配pattern,则跳转到最后。这种状况下匹配pattern的行能够避开执行后续的命令,被排除在外。

下一个例子中,咱们利用分支命令的跳转效果达到相似if语句的效果:

command1
/pattern/b end command2 :end command3

当执行到/pattern/b end时,若是匹配pattern,则跳转到:end标签所在的位置,跳过command2而不执行。

进一步地,利用两个分支命令能够达到if..else的分支效果:

command1
/pattern/b dothree command2 b :dothree command3

这个例子中,当执行到/pattern/b dothree时,若匹配pattern则中转到:dothree标签,此时执行command3;若不匹配,则执行command2,而且跳转到最后。

上面的例子都是用到了分支命令,分支命令的跳转是无条件的。而与之相对的是测试命令,测试命令的跳转是有条件的,当且仅当当前行发生成功的替换时才跳转。

为了说明测试命令的用法,咱们用它来实现前文定义过的join_lines函数:

$ sed ':a;$!N;s/\n/,/;ta' text 1,11,2,11,22,111,222

在最前面咱们添加了一个标签:a,而后在再最后面利用测试命令跳转到该标签。可能,你会以为这里也能够用分支命令,可是事实上分支命令会致使死循环,由于在它里他没有结束的条件。

可是测试命令就不一样了,这一点直到最后才体现出来。当最后一行被N命令读入以后,回车替换成逗号,此时ta继续跳转到最开头,由于全部行都已经被读入,因此$!不匹配,同时模式空间中的回车已经所有被替换成逗号,因此替换也不会发生。以前咱们说过,当且仅当当前行发生成功的替换时测试命令才跳转。因此此时跳转不会发生,退出sed命令。

咱们能够利用sedsed这个工具来验证上面的过程,sedsed能够用来调试sed脚本。

$ ./sedsed -d -e ':a;$!N;s/\n/,/;ta' text PATT:1$ HOLD:$ COMM::a COMM:$ !N PATT:1\n11$ HOLD:$ COMM:s/\n/,/ PATT:1,11$ HOLD:$ COMM:t a COMM:$ !N PATT:1,11\n2$ HOLD:$ ... ... COMM:$ !N PATT:1,11,2,11,22,111,222\n1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:t a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:t a PATT:1,11,2,11,22,111,222,1111$ HOLD:$ 1,11,2,11,22,111,222,1111

看第27行替换命令发生的时候,此时模式空间的内容为PATT:1,11,2,11,22,111,222,1111$,所以替换失败,ta命令不会发生跳转,脚本执行退出。

而若是在这里把测试命令换成分支命令,整个执行过程就会陷入死循环了:

COMM:b a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:s/\n/,/ PATT:1,11,2,11,22,111,222,1111$ HOLD:$ COMM:b a COMM:$ !N PATT:1,11,2,11,22,111,222,1111$

高级命令总结

到此为止,全部高级命令的用法就已经介绍完了。最后一段内容因为时间的关系,写得比较仓促。高级命令的用法比起基础命令相对复杂一点,并且容易出错,须要十分当心,若是不肯定能够用上面介绍的sedsed工具来调式下,并且便于加深各类命令行为的理解。

相信你们确定用过grep这个命令,它能够找出匹配某个正则表达式的行,例如查看包含"the word"的行:

$ grep "the word" filename

可是grep是针对单行做匹配的,因此若是一个短句跨越了两行就没法匹配。这就给咱们出了一个题目,如何用sed模仿grep的行为,而且支持跨行短句的匹配呢?

当单词仅出如今一行时很简单,用普通的正则就能够匹配,这里咱们用到分支命令,当匹配时则跳转到最后:

/the word/b

当单词跨越两行,容易想到用N命令将下一行读取到模式空间再作处理。例如咱们一样要查找短句"the word",如今的文本是这样的:

$ cat text
we want to find the phrase the
word, but it appears across two lines.

当用N玲读入下一行时,模式空间的内容以下所示:

Pattern space: we want to find the phrase the\nword, but it appears across two lines.

所以,须要将回车符号删除,而后再做匹配。

$!N
s/ *\n/ /
/the word/b

但是这里会有一个问题,若是短句刚好在读入的第二行的话,虽然匹配,可是会打印出模式空间中的全部内容,这样不符合grep的行为,只显示包含短句的那一行。因此这里要再加一个处理,将模式空间的第一行内容删除,再在第二行中匹配。可是在此以前,首先要保存模式空间的内容,不然可没有后悔药可吃。

h命令能够将模式空间的内容保存到保持空间,而后利用替换命令将模式空间的第一行内容清除,而后再做匹配处理:

$!N
h
s/.*\n//
/the word/b

若是不匹配,则短句可能确实是跨越两行,这时候咱们首先用g命令将以前保存的内容从保持空间取回到模式空间,替换掉回车后再进行匹配:

g
s/ *\n/ /
/the word/b

这里若是匹配则直接跳转到最后,而且输出的内容是替换后的内容。

可是咱们要输出的是匹配的原文,而原文如今在保持空间还有一份拷贝,所以当匹配时,须要将原文从保持空间取回:

g
s/ *\n/ /
/the word/{
g
b
}

一样地,咱们要考虑不匹配的状况,不匹配的时候也要将会原文从保持空间取回,而且将模式空间的第一行删除,继续处理下一行:

g
s/ *\n/ /
/the word/{
g
b
}
g
D

将全部的sed脚本合在一块儿,假设咱们将如下内容保存到phrase.sed文件中:

/the word/b
$!N
h
s/.*\n//
/the word/b
g
s/ *\n//
/the word/{
g
b
}
g
D

接下来,咱们用一段文原本测试下以上的脚本是否正确:

$ cat text
We will use phrase.sed to print any line which contains the word. Or if the word appears across two lines, like below: It will print this line, because the word appears across two lines. You can run sed -f phrase.sed text to test this.

执行命令以下所示:

$ sed -f phrase.sed text the word. Or if the word appears across two lines, like It will print this line, because the word appears across two lines.

上面的命令中的"the word"其实能够是一个变量,这样咱们就能够将这个功能写成一个脚本或者函数,用在更多地方:

$ cat phrase.sh #! /bin/sh # phrase -- search for words across lines # $1 = search string; remaining args = filenames search=$1 for file in ${@:2}; do sed "/$search/b \$!N h s/.*\n// /$search/b g s/ *\n/ / /$search/{ g b } g D" $file done $ chmod +x phrase.sh $ ./phrase.sh 'the word' text the word. Or if the word appears across two lines, like It will print this line, because the word appears across two lines.

这只是一个开头,或者你也能够在此基础上扩展更多的功能。sed的命令从单个看来并没是很复杂,可是要用得好就有点难度了,因此须要多多实践,多多思考,这一点跟正则表达式的学习是同样的。若是以为没有现成的学习环境,sed1line是一个不错的开始。

相关文章
相关标签/搜索