在sed中使用循环
在sed中使用循环
循环通常都可以写为条件转移的形式。sed没有专门的循环语句,但提供了转移的命令,因而我们仍然可以实现循环。本篇中总结用sed进行循环的几种方式。sed处理文本的方式本身就是一种循环:
do while not EOF read line ... do sth end do。
1 在sed中进行判断
因为sed只处理字符和行号,它只能通过式样来作字串的匹配判断或者对行号进行判断。所以判断的条件需要以字串或行号的形式出现。
1.1 用holdspace储存标志位
# 进行6次操作
1{x; s/^/654321/; x}
:a
x;
/./{ s/.//; x; s/reg/ex/; ba}
x;
do sth else;
# 对每一行进行6次替换操作
1{x; s/^/654321/; x}
G
:a
/\n./{ s/reg/ex/; s/\n.$//; tb; s/.$//; ba}
:b
do sth else
1.2 用pattern space储存标志位
用pattern space(标志位附加在前面或后面),与hs的方式基本一样只是将标志位放在ps中。
1{ s/^/654321\n/ }
:a
/.\n/{ s/.//; s/reg/ex/; ba }
do sth else
1.3 用地址进行判断
地址(常用的地址是1,$)。当循环的条件与地址或行号有关时可以以这种方式。
sed '/./{H;d};x;/re/p' # 显示某个段落
2,8{H;d}; $G # 类似ed中的:2,8m$。d在这里有两个作用a清空PS。b强制进入下一cycle。
1.4 用式样进行判断
以当前ps的内容作为判断的标准,当循环条件与输入的内容有关时可以用这种方法。这样方法与标志位的方法相似,所不同的是我们并不人为地设置标志而是以当前ps的内容作为标志。请参考下面的例子:
:a do sth with regexp /regexp/ba
如果中间有s命令我们常用t来跳转,因而上面的可以写为:
:a
s/regexp/blah/
ta
2 循环常用的命令解析
q, b, t, T, d, n, N, :label
2.1 b/t控制循环
b都是sed中的分支(跳转)命令。它的格式是`b label‘,也可以去掉中间的空格,写作`blabel‘。上面命令的作用是从:label处继续执行脚本。label必须在脚本中定义,方法是在前面加上冒号(:),如`:loop‘`:lable‘。大部分sed对标签的长度有限制,具体的限制可以参考sed faq。如果b后没有带标签,则默认转到脚本结束处。
要注意的是许多的sed不允许在后面接其他命令因而:
gsed 'b abc;s/^/eee/;:abc;…'
这样的命令在其他版本的sed中要写成:
sed 'b abc
s/^/eee/
:abc'
或:
sed -e 'b abc' -e 's/^/eee/; :abc'
t命令与b相似。不同点在于t是以前s命令的成功与否来决定是否跳转,如成功则跳转。如果在一个s命令后使用了多个条件跳转则第二个及其后的t都会失败。gnu sed还提供了与t相反的T命令。
$ echo abc|sed 's/a/a/;bb;:b;tc;s/^/zzz/;:c' abc $ echo abc|sed 's/a/a/;tb;:b;tc;s/^/zzz/;:c' zzzabc $ echo abc|sed 's/a/a/;tb;:b;tc;s/^/zzz/;:c' zzzabc
b/t控制循环:
s/re/&/;t
s/re/&/;T
与
/re/b
/re/!b
是等价的。但在下面的内容中我们会看到s/t的组合可以用在一些更复杂的情况。
当作用b进行循环时常用下面的方式退出循环:
在b前使用式样或地址(循环条件):
:a ... /regexp/ba
上面的情况通常要求中间的语句对/regexp/进行修改或有另外的退出命令,才不会成为死循环。
2.1.0.1 退出条件
使用b/t循环时,为了避免出现死循环通常要设置退出的条件。如:
:a ... /xxx/b ... /regexp/ba
除了用b外还可以用t, N, q, d作为退出的命令,用式样或行号作为退出的条件。当前面有成功的替换时,可用`t‘转移动脚本中的任意位置——当然也可以跳出循环体。在最后一行使用N会退出脚本,利用这一点我们可以退出循环体。不过不同版本的sed在最后一行使用`N‘时的结果不同——有的会显示ps的内容,有的则不会。`q‘命令用来退出脚本,当然也就不会再循环了。`d‘和`D‘命令的副作用是强制脚本进入下cycle,这使得我们可以用这两个命令来形成行与行之间的循环,如果下一循环中不满足循环的进入条件则循环中止——所以这两个命令既是进行循环的命令也是退出的命令。当然这些行前面都可以使用式样或行号作为运行命令的条件。
如果中间的语句没有对/regexp/修改则结果类似:
:a ... ba
例:要将输入中#号后的所有xxx删除。
输入: abc#efxxxghxxxxijxxx 输出: abc#efghxij sed ':a s/\(#.*\)xxx/\1/ # 修改了循环标志 /#.*xxx/ba'
当然这时t可以派上用场了:
sed ':a; s/\(#.*\)xxx/\1/; ta'
下面是一些实例:
echo -e "abc\nefg" | sed ':a;/re/b;ba' echo -e "abc\nefg" | sed ':a;/re/!b;ba' echo -e "abc\nefg" | sed ':a;s/re/&/;t;ba' echo -e "abc\nefg" | gsed ':a;s/re/&/;T;ba' 用以控制循环: echo -e "abc\nefg" | sed ':a;/re/ba' echo -e "abc\nefg" | sed ':a;/re/!ba' echo -e "abc\nefg" | sed ':a;s/re/&/;ta' echo -e "abc\nefg" | sed ':a;s/re/&/;Ta' echo -e "abc\nefg" | sed '/re/b; :'
2.2 d/D控制循环
用d来控制循环也是sed脚本中比较常用的技巧。d之所以能用来控制循环主要是因为它在删除完之后不会执行接下来的命令而会直接进入下一个cycle中。例:
sed 'd; s/^/abc/' # s命令将不会被执行
2.3 标志位控件循环
在前面我们已经说过sed中进行条件判断的一些方法。这些方法除了是进入循环的条件也可以作为退出循环的条件。这里举个例子:
G;s/$/123456789/ # 循环9次 :loop s/\n$//;t break # 退出循环 ... do sth # 进行操作 s/.$// # 减一操作 b loop # next
2.4 N作为退出条件
N添加下一行到当前PS。如果在最后一行时运行了N,sed通常会退出。如果在循环中使用则会退出循环。需要注意的是不同的N对最后一行的处理是不同的,一些版本在最后一行执行N时会安静的退出。而另一些版本如GNU sed将会默认显示PS的内容再退出。
见下面的例子:
echo -e "abc\nefg" | sed ':a;ba' echo -e "abc\nefg" | sed ':a;n;ba' echo -e "abc\nefg" | sed ':a;N;ba' echo -e "abc\nefg" | sed ':a;$!n;ba' echo -e "abc\nefg" | sed ':a;$!N;ba'
第一个例子中sed会进入死循环。第二三个例子会正常退出。其中第二个例子会显示输入,而第三个例子的行为与sed的版本有关,GNU sed会显示输入。最后一个例子会进入死循环,前面说过在$!N可以让N在所有的sed版本中显示结果。但仍要判断使用的时机。
3 实例
在sed中使用循环的例子相当多,这里举两个例子。
# “sed单行脚本”中的一个例子
# 以79个字符为宽度,将所有文本右对齐
sed -e :a -e 's/^.\{1,78\}$/ &/;ta' # 78个字符外加最后的一个空格
# 将2至8行移到文件末尾
# 类似ed中的:2,8m$。
# d在这里有两个作用,a、清空PS。b、强制进入下一cycle。
# do while 2<=linenum<=8; H; end do
sed '2,8{H;d}; $G'
[–结束–]
![hq00e[a]126.com](http://static.flickr.com/56/120355805_7079a475f9_m.jpg)



网友“无奈何”来信指出文中示例有错并给出了修改后的脚本。感谢“无奈何”网友,这是原邮件的内容:
在读到《在sed中使用循环》中关于标志位控件循环一节时有些疑问特此请教。您提供的sed示例脚本:
============
G;s/$/123456789/ # 循环9次
:loop
s/n$//;t break # 退出循环
… do sth # 进行操作
s/.$// # 减一操作
b loop # next
============
总也无法正确执行,好像有些问题。利用您提供的脚本构架创建测试代码如下:
test1
============
G;s/$/123456789/
:loop
s/n$//;t break
s/^/#/
s/.$//
b loop
:break
============
其中 s/^/#/ 等句总是不被执行,深究其原因了解到,T 函数测试在当前输入行上是否进行成功替换,而语句 s/$/123456789/ 总是成功执行,所以进入 loop 段后一旦执行t命令就会跳转,其下语句总是不能执行。不知道您对上面的分析作何看法?再者 T 函数已执行了一个成功的替换函数在读入一个新行或执行一个 T 函数时将被重置。对此做两个测试片断如下:
test2
============
G;s/$/123456789/
t loop
:loop
s/n$//;t break
s/^/#/
s/.$//
t loop
:break
============= test3
=============
G;s/$/123456789/
:loop
/n$/t break
s/^/#/
s/.$//
b loop
:break
s/n$//
=============
Comment by hq00e — 2006, July 25 @ 11:49