本篇将会介绍sed提供的更多高级特性,这些功能你未必会经常用到,但当需要时,知道这些功能的存在以及如何使用肯定是件好事。
一、 多行命令
在使用sed的基础命令时,你可能注意到了一个局限。前面的sed命令都是针对单行数据执行操作的。在sed读取数据流时,它会基于换行符的位置将数据分成行,然后根据定义好的脚本命令一次处理一行数据,再移到下一行重复这个过程。
有时需要对跨多行的数据执行特定操作,如果要查找或替换一个短语,就更是如此了。举个例子,如果你正在数据中查找短语Linux System Administrators Group,它很有可能出现在两行中,每行各包含其中一部分短语。如果用普通的sed命令来处理文本,就不可能发现这种被分开的短语。
sed包含了三个可用来处理多行文本的特殊命令:
- N:将数据流中的当前匹配行和下一行加入模式空间,创建一个多行组来处理
- D:删除多行组中的一行
- P:打印多行组中的一行
1. 单行next 命令 n
先看看单行版本的next命令是如何工作的,然后会比较容易理解多行版本的next命令是如何操作的。
单行next命令会将数据流中的下一文本行移动到sed的模式空间。
在这个例子中,你有个数据文件,共有5行内容,其中的两行是空的。目标是删除首行之后的空白行,而留下最后一行之前的空白行。
如果按照前篇学到的脚本,你会删掉两个空白行。
cat data1.txt
This is the header line.
This is a data line.
This is the last line.
sed '/^$/d' data1.txt
由于要删除的行是空行,没有任何能够标示这种行的文本可供查找。解决办法是用n命令。
sed '/header/{n ; d}' data1.txt
首先,脚本查找含有单词header的那一行。找到之后,n命令会将当前数据流中的下一行(空行)移动到sed的模式空间,然后对模式空间中的空行执行d命令删除。执行完d命令后,从数据流中读取下一行文本,并从头开始执行命令脚本,因为sed再也找不到包含单词header的行了,所以也不会有其他行会被删掉。
2. 多行next 命令 N
命令N会将当前匹配行和下一行添加到模式空间中。这会将数据流中的两行合并到同一个模式空间中,文本行仍然用\n分隔,但sed现在会将两行文本当成一行来处理。
cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
sed '/first/{ N ; s/\n/ / }' data2.txt #换行符替换为空格
首先查找含有first的文本,找到后,N命令会将first行和下一行(second)合并到模式空间,文本行仍然用\n分隔,但sed现在会将两行文本当成一行来处理。然后命令s将换行符替换成空格,结果是,文本文件中的两行在sed的输出中成了一行。
如果要在数据文件中查找一个可能会分散在两行中的文本短语的话,这是个很实用的程序。
cat data3.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.
如果短语分散在两行中的话,替换命令就没法直接识别匹配的模式,这时N命令就可以派上用场了。
sed 'N ; s/System.Administrator/Desktop User/' data3.txt
#注意System和Administrator间有个. 用来来匹配空格和换行符
On Tuesday, the Linux Desktop User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
注意,替换命令在System和Administrator之间用了通配符模式(.)来匹配空格和换行符这两种情况。但当它匹配了换行符时,就从字符串中删掉了换行符,导致两行合并成一行,这可能不是你想要的。
要解决这个问题,可以在sed脚本中直接用两个替换命令:一个用来匹配短语出现在多行中的情况,一个用来匹配短语出现在单行中的情况。
$ sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data3.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
这个脚本中仍有个小问题。这个脚本总是在执行sed命令前将下一行文本读入到模式空间。当它到了最后一行文本时,就没有下一行可读了,所以N命令会叫sed停止。
如果要匹配的文本正好在数据流的最后一行上,命令就不会发现要匹配的数据。
$ cat data4.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
$
$ sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.
由于System Administrator文本出现在了数据流中的最后一行,N命令会错过它,因为没有其他行可读入到模式空间跟这行合并。你可以轻松地解决这个问题——将单行命令放到N命令前面,并将多行命令放到N命令后面,像这样:
$ sed '
> s/System Administrator/Desktop User/
> N
> s/System\nAdministrator/Desktop\nUser/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
现在,查找单行中短语的替换命令在数据流的最后一行也能正常工作,多行替换命令则会负责短语出现在数据流中间的情况。
21.1.2 多行删除命令
前篇提到了单行删除命令(d)。 sed用它来删除模式空间中的当前行。但和N命令一
起使用时,使用单行删除命令就要小心了。
$ sed 'N ; /System\nAdministrator/d' data4.txt
All System Administrators should attend.
$
删除命令会在不同的行中查找单词System和Administrator,然后在模式空间中将两行都删掉。
这未必是你想要的结果。
sed提供了多行删除命令D,它只删除模式空间中的第一行,删除到换行符 (含换行符)为止的所有字符。
$ sed 'N ; /System\nAdministrator/D' data4.txt
Administrator's group meeting will be held.
All System Administrators should attend.
$
文本的第二行被N命令加到了模式空间,但仍然完好。如果需要删掉目标数据字符串所在行
的前一文本行,它能派得上用场。
这里有个例子,它会删除数据流中出现在第一行前的空白行。
$ cat data5.txt
This is the header line.
This is a data line.
This is the last line.
$
$ sed '/^$/{N ; /header/D}' data5.txt
This is the header line.
This is a data line.
This is the last line.
$
sed脚本会查找空白行,然后用N命令来将下一文本行添加到模式空间。如果新的模式
空间内容含有单词header,则D命令会删除模式空间中的第一行。如果不结合使用N命令和D命令,就不可能在不删除其他空白行的情况下只删除第一个空白行。
21.1.3 多行打印命令
多行打印命令(P)沿用了同样的方法,它只打印多行模式空间中的第一行。当你用-n选项来阻止脚本输出时,它和显示文本的单行p命令的用法大同小异。
$ sed -n 'N ; /System\nAdministrator/P' data3.txt
On Tuesday, the Linux System
$
当多行匹配出现时, P命令只会打印模式空间中的第一行。多行P命令的强大之处在和N命令
及D命令组合使用时才能显现出来。
D命令的独特之处在于强制sed返回到脚本的起始处,对同一模式空间中的内容重新执
行这些命令(它不会从数据流中读取新的文本行)。在命令脚本中加入N命令,你就能单步扫过整个模式空间,将多行一起匹配。
接下来,使用P命令打印出第一行,然后用D命令删除第一行并绕回到脚本的起始处。一旦返
回, N命令会读取下一行文本并重新开始这个过程。这个循环会一直继续下去,直到数据流结束。
21.2 保持空间
模式空间(pattern space)是一块活跃的缓冲区,在sed执行命令时它会保存待检查的
文本。但它并不是sed保存文本的唯一空间。
sed有另一块称作保持空间(hold space)的缓冲区域。在处理模式空间中的某些行时,
可以用保持空间来临时保存一些行。有5条命令可用来操作保持空间
这些命令用来将文本从模式空间复制到保持空间。这可以清空模式空间来加载其他要处理的
字符串。
通常,在使用h或H命令将字符串移动到保持空间后,最终还要用g、 G或x命令将保存的字符串移回模式空间(否则,你就不用在一开始考虑保存它们了)。
由于有两个缓冲区域,弄明白哪行文本在哪个缓冲区域有时会比较麻烦。这里有个简短的例
子演示了如何用h和g命令来将数据在sed缓冲空间之间移动。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed -n '/first/ {h ; p ; n ; p ; g ; p }' data2.txt
我们来一步一步看上面这个代码例子:
(1) sed脚本在地址中过滤出含有单词first的行;
(2) 当含有单词first的行出现时, h命令将该行放到保持空间;
(3) p命令打印模式空间也就是第一个数据行的内容;
(4) n命令提取数据流中的下一行(This is the second data line),并将它放到模式空间;
(5) p命令打印模式空间的内容,现在是第二个数据行;
(6) g命令将保持空间的内容(This is the first data line)放回模式空间,替换当
前文本;
(7) p命令打印模式空间的当前内容,现在变回第一个数据行了。
通过使用保持空间来回移动文本行,你可以强制输出中第一个数据行出现在第二个数据行后
面。如果去掉第一个p命令,你可以以相反的顺序输出这两行。
$ sed -n '/first/ {h ; n ; p ; g ; p }' data2.txt
这是个有用的开端。你可以用这种方法来创建一个sed脚本将整个文件的文本行反转!但要
那么做的话,你需要了解sed的排除特性,也就是下节的内容
21.3 排除命令
你也可以配置命令使其不要作用到数据流中的特定地址或地址区间。
感叹号命令(!)用来排除(negate)命令,也就是让原本会起作用的命令不起作用。
下面的例子演示了这一特性。
sed -n '/header/!p' data2.txt
普通p命令只打印data2文件中包含单词header的那行。加了感叹号之后,情况就相反了:除
了包含单词header那一行外,文件中其他所有的行都被打印出来了。
感叹号在有些应用中用起来很方便。前面演示过sed的N命令无法处理数据流中最后一行文本,因为之后再没有其他行了,可以用感叹号来解决这个问题。
$ sed 'N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.
$
$ sed '$!N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
$表示数据流中的最后一行文本,所以当sed到了最后一行时,它没有执行N命令,但它对所有其他行都执行了这个命令。
使用这种方法,你可以反转数据流中文本行的顺序。要实现这个效果(先显示最后一行,最
后显示第一行),你得利用保持空间做一些特别的铺垫工作。
你得像这样使用模式空间:
(1) 在模式空间中放置一行;
(2) 将模式空间中的行放到保持空间中;
(3) 在模式空间中放入下一行;
(4) 将保持空间附加到模式空间后;
(5) 将模式空间中的所有内容都放到保持空间中;
(6)重复执行第(3)~(5)步,直到所有行都反序放到了保持空间中;
(7) 提取并打印行。
图21-1详细描述了这个过程。
在使用这种方法时,你不想在处理时打印行。这意味着要使用sed的-n命令行选项。下一步
是决定如何将保持空间文本附加到模式空间文本后面。这可以用G命令完成。唯一的问题是你不
想将保持空间附加到要处理的第一行文本后面。这可以用感叹号命令轻松解决:
1!G
下一步就是将新的模式空间(含有已反转的行)放到保持空间。这也非常简单,只要用h命
令就行。
将模式空间中的整个数据流都反转了之后,你要做的就是打印结果。当到达数据流中的最后
一行时,你就知道已经得到了模式空间的整个数据流。打印结果要用$p
这些都是你创建可以反转行的sed脚本所需的操作步骤。现在可以运行一下试试:
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed -n '{1!G ; h ; $p }' data2.txt
sed脚本的执行和预期的一样。脚本输出反转了文本文件中原来的行。这展示了在sed
脚本中使用保持空间的强大之处。它提供了一种在脚本输出中控制行顺序的简单办法。
有个Linux命令已经有反转文本文件的功能了。 tac命令会倒序显示一个文本文件。这个命令的名字很巧妙,它执行的正好是与cat命令相反的功能
21.4 改变流
类似编程语言中的分支和循环,sed也可以改变数据流向。
21.4.1 分支
sed提供了一种方法,可以基于地址、地址模式或地址区间排除一整块命令,这允许你只对数据流中的特定行执行一组命令。
分支(branch)命令b的格式如下:
[address]b [label]
address参数决定了哪些行的数据会触发分支命令,label参数定义了要跳转到的位置。如果没有加label参数,跳转命令会跳转到脚本的结尾。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed '{2,3b ; s/This is/Is this/ ; s/line./test?/}' data2.txt
Is this the header test?
This is the first data line.
This is the second data line.
Is this the last test?
$
分支命令在数据流中的第2行和第3行处跳过了两个替换命令。
要是不想直接跳到脚本的结尾,可以为分支命令定义一个要跳转到的标签。标签以冒号开始,
最多可以是7个字符长度,将它加到b命令后即可。
:label2
$ sed '{/first/b jump1 ; s/This is the/No jump on/
> :jump1
> s/This is the/Jump here on/}' data2.txt
No jump on header line
Jump here on first data line
No jump on second data line
No jump on last line
$
跳转命令指定如果文本行中出现了first,程序应该跳到标签为jump1的脚本行。如果分支
命令的模式没有匹配, sed会继续执行脚本中的命令,包括分支标签后的命令(因此,所
有的替换命令都会在不匹配分支模式的行上执行)。
如果某行匹配了分支模式, sed就会跳转到带有分支标签的那行。因此,只有最后一
个替换命令会执行。
也可以跳转到脚本中靠前面的标签上,这样就达到了循环的效果。为了避免死循环,不停地查找逗号,可以为分支命令指定一个地址模式来查找。如果没有模式,跳转就应该结束。
$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> /,/b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
$
现在分支命令只会在行中有逗号的情况下跳转。在最后一个逗号被删除后,分支命令不会再
执行,脚本也就能正常停止了。
21.4.2 测试
类似于分支命令,测试(test)命令(t)也可以用来改变sed脚本的执行流程。测
试命令会根据替换命令的结果跳转到某个标签,而不是根据地址进行跳转。
如果替换命令成功匹配并替换了一个模式,测试命令就会跳转到指定的标签。如果替换命令
未能匹配指定的模式,测试命令就不会跳转。
测试命令使用与分支命令相同的格式。
[address]t [label]
跟分支命令一样,在没有指定标签的情况下,如果测试成功, sed会跳转到脚本的结尾。
测试命令提供了对数据流中的文本执行基本的if-then语句的一个低成本办法。举个例子,
如果已经做了一个替换,不需要再做另一个替换,那么测试命令能帮上忙。
$ sed '{
> s/first/matched/
> t
> s/This is the/No match on/
> }' data2.txt
No match on header line
This is the matched data line
No match on second data line
No match on last line
$
第一个替换命令会查找模式文本first。如果匹配了行中的模式,它就会替换文本,而且测
试命令会跳过后面的替换命令。如果第一个替换命令未能匹配模式,第二个替换命令就会被执行。
有了测试命令,你就能结束之前用分支命令形成的无限循环。
$ echo "This, is, a, test, to, remove, commas. " | sed -n '{
> :start
> s/,//p
> t start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
当无需替换时,测试命令不会跳转而是继续执行剩下的脚本。
21.5 模式替代
你已经知道了如何在sed命令中使用模式来替代数据流中的文本。然而在使用通配符时,很
难知道到底哪些文本会匹配模式。
举个例子,假如你想在行中匹配的单词两边上放上引号。如果你只是要匹配模式中的一个单
词,那就非常简单。
$ echo "The cat sleeps in his hat." | sed 's/cat/"cat"/'
The "cat" sleeps in his hat.
$
但如果你在模式中用通配符(.)来匹配多个单词呢?
$ echo "The cat sleeps in his hat." | sed 's/.at/".at"/g'
The ".at" sleeps in his ".at".
$
模式字符串用点号通配符来匹配at前面的一个字母。遗憾的是,用于替代的字符串无法匹配
已匹配单词中的通配符字符。
21.5.1 &符号
&符号可以用来代表替换命令中的匹配的模式。不管模式匹配的是什么样的文本,你都可以在替代模式中使用&符号来使用这段文本。这样就可以操作模式所匹配到的任何单词了。
$ echo "The cat sleeps in his hat." | sed 's/.at/"&"/g'
The "cat" sleeps in his "hat".
$
当模式匹配了单词cat, "cat"就会出现在了替换后的单词里。当它匹配了单词hat, "hat"
就出现在了替换后的单词中。
21.5.2 替代单独的单词
&符号会提取匹配替换命令中指定模式的整个字符串。有时你只想提取这个字符串的一部分。
sed用圆括号来定义替换模式中的子模式。你可以在替代模式中使用特殊字符来引用
每个子模式。替代字符由反斜线\和数字组成。数字表明子模式的位置。当在替换命令中使用圆括号时,必须用转义字符将它们标示为分组字符而不是普通的圆括号。这跟转义其他特殊字符正好相反。
sed会给第一个子模式分配字符\1,给第二个子模式分配字符\2,依此类推。
来看一个在sed脚本中使用这个特性的例子。$ echo "The System Administrator manual" | sed '
> s/\(System\) Administrator/\1 User/'
The System User manual
例子将System Administrator替换为System User,其中\1就代表圆括号中的System,把它和User组合起来作为s命令的替换目标
也可以使用通配符
$ echo "That furry cat is pretty" | sed 's/furry \(.at\)/\1/'
That cat is pretty
$
$ echo "That furry hat is pretty" | sed 's/furry \(.at\)/\1/'
That hat is pretty
$
在这种情况下,你不能用&符号,因为它会替换整个匹配的模式。子模式提供了答案,允许
你选择将模式中的某部分作为替代模式。
当需要在两个或多个子模式间插入文本时,这个特性尤其有用。这里有个脚本,它使用子模
式在数字中插入逗号。
$ echo "1234567" | sed '{
> :start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start
> }'
1,234,567
$
这个脚本将匹配模式分成了两部分。
.*[0-9]
[0-9]{3}
这个模式会查找两个子模式。第一个子模式是以数字结尾的任意长度的字符。第二个子模式
是若干组三位数字。如果这个模式在文本中找到了,替代文本会在两个子模式之间加一个逗号,每个子模式都会通过其位置来标示。
这个脚本使用测试命令来遍历这个数字,直到放置好所有的逗号。
21.6 在脚本中使用 sed
现在你已经认识了sed的各个部分,是时候将它们综合运用在shell脚本中了。本节将
会演示一些你应该知道的特性,在脚本中使用sed时会用得着它们。
21.6.1 使用包装脚本
你可能已经注意到,实现sed脚本的过程很烦琐,尤其是脚本很长的话。可以将sed命令放到shell包装脚本(wrapper)中,不用每次使用时都重新键入整个脚本。包装脚本充当着sed脚本和命令行之间的中间人角色。
在shell脚本中,可以将普通的shell变量及参数和sed脚本一起使用。这里有个将命令行
参数变量作为sed脚本输入的例子。
$ cat reverse.sh
#!/bin/bash
# Shell wrapper for sed editor script.
# to reverse text file lines.
#
sed -n '{ 1!G ; h ; $p }' $1
#
$
名为reverse的shell脚本用sed脚本来反转数据流中的文本行。它使用shell参数$1从命令
行中提取第一个参数,这正是需要进行反转的文件名。
$ ./reverse.sh data2.txt
This is the last line.
This is the second data line.
This is the first data line.
This is the header line.
$
现在你能在任何文件上轻松使用这个sed脚本,再不用每次都在命令行上重新输入了。
21.6.2 重定向 sed 的输出
默认情况下, sed会将脚本的结果输出到STDOUT上。你可以在shell脚本中使用各种标
准方法对sed的输出进行重定向。
可以在脚本中用$()将sed命令的输出重定向到一个变量中,以备后用。
下面的例子使用sed脚本来向数值计算结果添加逗号。
$ cat fact.sh
#!/bin/bash
# Add commas to number in factorial answer
#
factorial=1
counter=1
number=$1
#
while [ $counter -le $number ]
do
factorial=$[ $factorial * $counter ]
counter=$[ $counter + 1 ]
done
#
result=$(echo $factorial | sed '{
:start
s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
t start
}')
#
echo "The result is $result"
#
$
$ ./fact.sh 20
The result is 2,432,902,008,176,640,000
$
在使用普通的阶乘计算脚本后,脚本的结果会被作为sed脚本的输入,它会给结果加上逗号。然后echo语句使用这个值产生最终结果。
21.7 创建 sed 实用工具
本节展示了一些方便趁手、众所周知的sed脚本,可以帮助我们进行常见的数据处理工作。
21.7.1 文本间添加空白行
首先,让我们看一个向文本文件的行间插入空白行的简单sed脚本。
$cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
看起来相当简单!这个技巧的关键在于保持空间的默认值。记住, G命令会简单地将保持空间内容附加到模式空间内容后。当启动sed时,保持空间只有一个空行。将它附加到已有行后面,你就在已有行后面创建了一个空白行。
sed 'G' data2.txt
你可能已经注意到了,这个脚本在数据流的最后一行后面也加了一个空白行。如果你不想要这个空白行,可以用排除符号(!)和尾行符号($)来确保脚本不会将空白行加到数据流的最后一行后面。
$ sed '$!G' data2.txt
现在看起来好一些了。只要该行不是最后一行, G命令就会附加保持空间内容。当sed
到了最后一行时,它会跳过G命令。
21.7.2 对可能含有空白行的文件添加空白行
再进一步探索上面的例子:如果文本文件已经有一些空白行,用前面的脚本,有些区域会有太多的空白行,因为每个已有的空白行也会被加倍。
$ cat data6.txt
This is line one.
This is line two.
This is line three.
This is line four.
$
$ sed '$!G' data6.txt
这个问题的解决办法是,首先删除数据流中的所有空白行,然后用G命令在所有行后插入新的空白行。要删除已有的空白行,需要将d命令和一个匹配空白行的模式一起使用。
$ sed '/^$/d ; $!G' data6.txt
21.7.3 给文件中的行编号
前面演示过如何用等号来显示数据流中行的行号。
$ sed '=' data2.txt
这可能有点难看,因为行号是在数据流中实际行的上方。比较好的解决办法是将行号和文本
放在同一行。
你已经知道如何用N命令合并行,在sed脚本中使用这个命令应该不难。这个工具的技巧在于
不能将两个命令放到同一个脚本中。
在获得了等号命令的输出之后,你可以通过管道将输出传给另一个sed脚本,它会使
用N命令来合并这两行。还需要用替换命令将换行符更换成空格或制表符。
$ sed '=' data2.txt | sed 'N; s/\n/ /'
现在看起来好多了。在查看错误消息的行号时,这是一个很好用的小工具。
有些bash shell命令也可以添加行号,但它们会另外加入一些东西(有可能是不需要的间隔)。
$ nl data2.txt
$ cat -n data2.txt
21.7.4 打印末尾行
到目前为止,你已经知道如何用p命令来打印数据流中所有的或者是匹配某个特定模式的行。
如果只需处理一个长输出(比如日志文件)中的末尾几行,要怎么办呢?
美元符代表数据流中最后一行,所以只显示最后一行很容易。
$ sed -n '$p' data2.txt
This is the last line.
$
那么,如何用美元符来显示数据流末尾的若干行呢?答案是创建滚动窗口。
滚动窗口是检验模式空间中文本行块的常用方法,它使用N命令将这些块合并起来。 N命令将
下一行文本附加到模式空间中已有文本行后面。一旦你在模式空间有了一个指定行的文本块,你可以用美元符来检查你是否已经处于数据流的尾部。如果不在,就继续向模式空间增加行,同时删除原来的行(记住, D命令会删除模式空间的第一行)。
通过循环N命令和D命令,你在向模式空间的文本行块增加新行的同时也删除了旧行。分支命
令非常适合这个循环。要结束循环,只要识别出最后一行并用q命令退出就可以了。
最终的sed编辑器脚本看起来如下。
$ cat data7.txt
This is line 1.
This is line 2.
This is line 3.
This is line 4.
This is line 5.
This is line 6.
This is line 7.
This is line 8.
This is line 9.
This is line 10.
$ sed '{
> :start
> $q ; N ; 6,$D
> b start
> }' data7.txt
这个脚本会首先检查这行是不是数据流中最后一行。如果是,退出(quit)命令q会停止循
环。 N命令会将下一行附加到模式空间中当前行之后。如果当前行在第5行后面,6,$D命令会
删除模式空间中的第一行。这就会在模式空间中创建出滑动窗口效果。因此,这个sed程序脚本
只会显示出data7.txt文件最后10行。当然,你直接用tail命令也可以做到。
21.7.4 打印末尾行
到目前为止,你已经知道如何用p命令来打印数据流中所有的或者是匹配某个特定模式的行。
如果只需处理一个长输出(比如日志文件)中的末尾几行,要怎么办呢?
美元符代表数据流中最后一行,所以只显示最后一行很容易。
$ sed -n '$p' data2.txt
This is the last line.
$
那么,如何用美元符来显示数据流末尾的若干行呢?答案是创建滚动窗口。
滚动窗口是检验模式空间中文本行块的常用方法,它使用N命令将这些块合并起来。 N命令将
下一行文本附加到模式空间中已有文本行后面。一旦你在模式空间有了一个10行的文本块,你可
以用美元符来检查你是否已经处于数据流的尾部。如果不在,就继续向模式空间增加行,同时删
除原来的行(记住, D命令会删除模式空间的第一行)
21.7.5 删除行
另一个有用的sed工具是删除数据流中不需要的空白行。删除数据流中的所有空白行很容易,但要选择性地删除空白行则需要一点创造力。
- 删除连续的空白行
数据文件中出现多余的空白行会非常让人讨厌。通常,数据文件中都会有空白行,但有时由
于数据行的缺失,会产生过多的空白行。删除连续空白行的最简单办法是用地址区间来检查数据流。关键在于创建包含一个非空白行和一个空白行的地址区间。如果sed编辑
器遇到了这个区间,它不会删除行。但对于不匹配这个区间的行(两个或更多的空白行),它会删除这些行。
下面是完成这个操作的脚本。
/./,/^$/!d
区间是/./到/^$/。区间的开始地址会匹配任何含有至少一个字符的行。区间的结束地址会
匹配一个空行。在这个区间内的行不会被删除。
下面是实际的脚本。
$ cat data8.txt
This is line one.
This is line two.
This is line three.
This is line four.
$
$ sed '/./,/^$/!d' data8.txt
无论文件的数据行之间出现了多少空白行,在输出中只会在行间保留一个空白行。
2. 删除开头的空白行
删除数据流顶部的空白行不难,核心是
/./,$!d
这个脚本用地址区间来决定哪些行要删掉,从含有字符的行开始,一直到数据流结
束。在这个区间内的任何行都不会从输出中删除。这意味着含有字符的第一行之前的任何行都会删除。
来看看这个简单的脚本。
$ cat data9.txt
This is line one.
This is line two.
$ sed '/./,$!d' data9.txt
两种写法都可以
3. 删除结尾的空白行
很遗憾,删除结尾的空白行并不像删除开头的空白行那么容易,需要利用循环来实现。
在开始讨论前,先看看脚本是什么样的。
sed '{
:start
/^\n*$/{$d; N; b start }
}'
可能乍一看有点奇怪。注意,在正常脚本的花括号里还有花括号。这允许你在整个命令脚本
中将一些命令分组。该命令组会被应用在指定的地址模式上。地址模式能够匹配只含有一个换行
符的行。如果找到了这样的行,而且还是最后一行,删除命令会删掉它。如果不是最后一行, N
命令会将下一行附加到它后面,分支命令会跳到循环起始位置重新开始。
下面是实际的脚本。
$ cat data10.txt
This is the first line.
This is the second line.
$ sed '{
> :start
> /^\n*$/{$d ; N ; b start }
> }' data10.txt
21.7.6 删除 HTML 标签
现如今,从网站下载文本并将其保存或用作应用程序的数据并不罕见。但当你从网站下载文
本时,有时其中也包含了用于数据格式化的HTML标签。如果你只是查看数据,这会是个问题。
标准的HTML Web页面包含一些不同类型的HTML标签,标明了正确显示页面信息所需要的
格式化功能。这里有个HTML文件的例子。
$ cat data11.txt
<html>
<head>
<title>This is the page title</title>
</head>
<body>
<p>
This is the <b>first</b> line in the Web page.
This should provide some <i>useful</i>
information to use in our sed script.
</body>
</html>
$
HTML标签由<>来识别。 大多数HTML标签都是成对出现的:一个起始标签(比
如<b>用来加粗),以及另一个结束标签(比如</b>用来结束加粗)。
并且需要让sed忽略掉任何嵌入到原始标签中的大于号。要这么做的话,
你可以创建一个字符组来排除大于号:
s/<[^>]*>//g
可以加一条删除命令来删除多余的空白行。
$ sed 's/<[^>]*>//g ; /^$/d' data11.txt