Hedley

Stay Hungry, Stay Foolish.

Sed 循环处理

sedshell 编程中很常见的流编辑命令,主要用于批量替换。

sed 的默认编辑单位是『行』,如果要处理的信息分布于多个行中,需要考虑多行处理命令:N、P、Dclick here, nice tutorial

考虑以下情景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% cat onetwo                                                                            
one two onw two one two xxx one
two yyy one
two zzz one    
two kkk two one
twx xxx yyy one two one
xxxxx
xxx

yyy one 
x two one
xx one

two x
x one
two one
two

如果一行以 one 结尾,而且下一行以 two 开头,那么就把这两个邻接行拼接成一行,并把 one two 替换为 1 2

在只了解 N、P、D 之后,想出一个比较有意思的解决方案:

1
2
3
4
5
6
7
8
9
10
11
% cat onetwo.sed
#!/usr/bin/sed -f
/one *$/ {
        # 连接下一行,$!N 表示如果读到最后一行单独处理 
        $!N
        # 替换 prefix + one + 回车符 + two 为 回车符 + prefix + 1 2
        s/^\(.*\)one *\ntwo/\
\11 2/
        /^\n/ !P
        D
}

表达式 s/a/b/ 中,查找字符串 a 如果包含回车符,用 \n 表示。但是如果替换字符串 b 中如果包含回车符,需要用 \ + 回车 表示。并且回车后的替换内容要顶头,不能排版加空格、tab1

以上解决方案的关键点在于替换时在开头又加了一个换行符,如此后面执行 D 则删除了模式空间中的这个空行,继续读入下一行处理,以此达到循环处理的目的。

1
2
3
4
5
6
7
8
9
10
11
12
% ./onetwo.sed onetwo
one two onw two one two xxx 1 2 yyy 1 2 zzz 1 2 kkk two one
twx xxx yyy one two one
xxxxx
xxx

yyy one 
x two one
xx one

two x
x 1 2 1 2

后面了解了 t 命令后,更直接的解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
% cat onetwot.sed     
#!/bin/bash 
sed  '
/one/ {
        :again
                $!N
                s/one *\ntwo/1 2/
                t again

}
' $1

t 命令相当于一个 while,当 t 之前最近的一个 s/a/b/ 命令匹配成功时,继续循环。得到同样的运行结果。

此小实例只做趣味讨论,shell 中的流处理2都是面向3为单位的,对于内容有 overlap 的循环处理,效率低且不好用。




  1. 取决于不同的 sed 版本,但是顶头写肯定是 ok 的。

  2. sed, awk, grep etc..

  3. or 块,比如 awk 中的 FS=‘\n’