sed中的循环结构

Table of Contents



Up: (dir)

sed中的循环结构

这篇文章粗略地总结了一些在sed进行循环操作的方法。的确是很粗糙但为了在农历年前放上就不管那么多了。
by hq00e

一些缩写
ps=pattern space,式样空间
hs=hold space,保留空间
regexp=regular expression, 正则表达式

循环都可以写为条件转移的形式。如C语言中的if加goto的形式,但这与结构化编程的思想相违背。因而C语言中通常用for\while来写循环语句。但在被编译后程序的机器代码仍是以条件转移的形式出现的。sed没有专门的循环语句,但提供了转移的命令,因而我们仍然可以实现循环。本篇中总结用sed进行循环的几种方式。sed处理文本的方式本身就是一种循环:

do while not EOF
 read line
   ... do sth
 end do。



Next: ,
Up: Top

在sed中进行判断

因为sed只处理字符和行号,它只能通过式样来作字串的匹配判断或者对行号进行判断。所以判断的条件需要以字串或行号的形式出现。

用holdspace储存标志位

在hs中存储字串作为标志位,如:

# 进行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

用pattern space储存标志位

用pattern space(标志位附加在前面或后面),与hs的方式基本一样只是将标志位放在ps中。

1{ s/^/654321\n/ }
:a
/.\n/{ s/.//; s/reg/ex/; ba }
do sth else

用地址进行判断

地址(常用的地址是1,$)。当循环的条件与地址或行号有关时可以以这种方式。

sed '/./{H;d};x;/re/p' # 显示某个段落
2,8{H;d}; $G  # 类似ed中的:2,8m$。d在这里有两个作用a清空PS。b强制进入下一cycle。

用式样进行判断

以当前ps的内容作为判断的标准,当循环条件与输入的内容有关时可以用这种方法。这样方法与标志位的方法相似,所不同的是我们并不人为地设置标志而是以当前ps的内容作为标志。请参考下面的例子:

:a
 do sth with regexp
/regexp/ba

如果中间有s命令我们常用t来跳转,因而上面的可以写为:
:a
s/regexp/blah/
ta



Next: ,
Previous: sect1,
Up: Top

循环常用的命令解析

q, b, t, T, d, n, N, :label

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/进行修改或有另外的退出命令,才不会成为死循环。

退出条件

使用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; :'

d/D控制循环

用d来控制循环也是sed脚本中比较常用的技巧。d之所以能用来控制循环主要是因为它在删除完之后不会执行接下来的命令而会直接进入下一个cycle中。例:

sed 'd; s/^/abc/' # s命令将不会被执行

标志位控件循环

前面我们已经说过sed中进行条件判断的一些方法。这些方法除了是进入循环的条件也可以作为退出循环的条件。这里举个例子:

G;s/$/123456789/ # 循环9次
:loop
s/\n$//;t break  # 退出循环
... do sth       # 进行操作
s/.$//           # 减一操作
b loop           # next

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版本中显示结果。但仍要判断使用的时机。



Previous: sect2,
Up: Top

实例

在sed中使用循环的例子相当多,这里举几例。

# “sed单行脚本”中的一个例子
# 以79个字符为宽度,将所有文本右对齐
sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # 78个字符外加最后的一个空格

# 类似ed中的:2,8m$。
# d在这里有两个作用,a、清空PS。b、强制进入下一cycle。
# do while 2<=linenum<=8; H; end do
sed '2,8{H;d}; $G'

About this entry