烦恼一般都是想太多了。

0%

sed中的分支与流程控制

sed是一个非常强大的工具,对于文本编辑来说。然而,很多时候可能我们用不到这节介绍的分支跳转与流程控制,但是某些时候又是非常有用的。因为有些sed需要比较复杂来实现的功能,已经被用c来实现成了程序命令,所以不太实用了,不过对于想要专注于sed的同学们来说,学习这个是非常有必要的。

概述

分支命令:b, t, T可以用来改变sed的工作流程。

默认流程:读入一行到 pattern space,按序执行命令进行处理。没有地址指定的命令应用到所有行。

sed并不支持 if/then/else 语句。其使用几个命令来进行条件检测或改变执行流程:

  • d 清理当前 模式空间, 忽略后面未执行命令,并不打印模式空间,重新开始循环处理。
  • D 删除模式空间的内容直到第一个换行符,不打印模式空间,忽略后面的命令,重新开始循环。
    [addr]X
    [addr]{ X ; X ; X }
    /regexp/X
    /regexp/{ X ; X ; X }

地址和正则式可以用来作为 if/then 的条件:如果 [addr] 匹配当前的模式空间,执行命令。比如:/^#/d 表示 : 如果当前的模式空间匹配正则式^#(以 # 开头的行),然后就执行d命令:删除模式空间内的这行,不打印到标准输出,然后开始下一个循环。

  • b 无条件分支(跳转到标签,跳过或重复其他命令,并不重新开始一个循环)。结合地址使用,这个分支动作可以在特定的行上使用。
  • t 条件分支。读入一行后,当一个s///命令成功执行或其他条件分支动作后为真。
  • T 条件分支。与t相似,但是正好相反,只有当读入一行,并且没有成功的 s命令执行为真。

接下来的两个sed程序是等价的。第一个例子中,用b命令在包括l的行上跳过s///命令。第二个例子使用一个地址和!符号来在指定的行上进行替换。y///命令在所有行上执行。

$ printf '%s\n' a1 a2 a3 | sed -E '/1/bx ; s/a/z/ ; :x ; y/123/456/'
a4
z5
z6

$ printf '%s\n' a1 a2 a3 | sed -E '/1/!s/a/z/ ; y/123/456/'
a4
z5
z6

分支与循环

b, t, T后面可以跟随一个标签(一般是单个字母)。标签用一个冒号后跟一个或几个字母定义(比如,:x)。如果忽略了标签,那么会重新开始一个循环。要注意区别分支到标签和重新开始循环:当循环重新开始的时候,sed首先打印出当前的模式空间内容,然后读入下一行到模式空间。跳转到一个标签(即使是在程序开头)不会打印模式空间也不会读入下一行。

下面的程序是没有操作的。b命令(程序中唯一的命令)不带标签,所以其只是简单的重新开始一个循环。在每个循环中,模式空间内容被打印出来,并读取下一行:

$ seq 3 | sed b
1
2
3

下面的例子是一个无限循环—不会终止也不会打印任何东西。b命令跳转到x标签,但一个新的循环却永远不会开始:

$ seq 3 | sed ':x ; bx'
# The above command requires gnu sed (which supports additional
# commands following a label, without a newline). A portable equivalent:
# sed -e ':x' -e bx

分支几乎 用n, N命令配合完成的:两个命令都会读入下一行到模式空间,也都不等待循环重启。 在读入下一行前,n打印当前模式空间内容并清空,N附加一个换行符和下一行到当前的模式空间。

看看下面的两个例子:

$ seq 3 | sed ':x ; n ; bx'
1
2
3

$ seq 3 | sed ':x ; N ; bx'
1
2
3
  • 两个例子都不是无限的,也不开始一个新的循环。
  • 第一个例子中,n先打印模式空间内容,清空模式空间,再读入下一行。
  • 第二个例子中,N把下一行附加到模式空间(前面添加一个换行符)。行被累计起来直到无新行可读,然后N结束sed。在sed终止的时候,循环结束动作被执行,打印出整个模式空间。
  • 第二个例子需要 GNU sed,因为其使用的是非 POSIX-std 行为的N命令。
  • 为了更多的测试这两个例子,看看下面的命令:
    printf '%s\n' aa bb cc dd | sed ':x ; n ; = ; bx'
    printf '%s\n' aa bb cc dd | sed ':x ; N ; = ; bx'
    printf '%s\n' aa bb cc dd | sed ':x ; n ; s/\n/***/ ; bx'
    printf '%s\n' aa bb cc dd | sed ':x ; N ; s/\n/***/ ; bx'

分支例子:连接行

作为现实世界中使用分支的一个例子,在以=进行分行的文件中:

$ cat jaques.txt
All the wor=
ld's a stag=
e,
And all the=
men and wo=
men merely =
players:
They have t=
heir exits =
and their e=
ntrances;
And one man=
in his tim=
e plays man=
y parts.

下面的程序使用/=$/作为条件:如果当前模式空间以=结尾,就会用N读取下一行,替换所有的=和后面的换行符,接着无条件分支b到程序开头,这并不会开始一个新的循环处理。如果模式空间不是以=结尾,执行默认动作:打印模式空间,开始新的循环:

$ sed ':x ; /=$/ { N ; s/=\n//g ; bx }' jaques.txt
All the world's a stage,
And all the men and women merely players:
They have their exits and their entrances;
And one man in his time plays many parts.

还有个不同的办法:在所有行上(除了最后一行),N附加行到模式空间。一个s命令移除=\n。如果s成功执行,t跳转到程序开头(不完成也不重启循环)。如果s失败,t就不会跳转。然后,P会打印模式空间中第一个换行符前的内容,D删除到第一个换行符。

$ sed ':x ; $!N ; s/=\n// ; tx ; P ; D' jaques.txt
All the world's a stage,
And all the men and women merely players:
They have their exits and their entrances;
And one man in his time plays many parts.