Skip to content

Sed 循环

与传统的编程语言一样,sed 提供了控制执行流程的机制,特别是通过 label(标签)和分支命令。这使得创建 sed 命令的循环和条件执行成为可能,从而实现更复杂的文本处理逻辑。

sed 中的 label(标签)标记了脚本中的一个特定点。然后你可以使用分支命令跳转到这个 label。label 通过冒号(:)后跟标签名称来定义。标签名称可以包含除换行符之外的任何字符。

标签定义示例:
:mylabel
:start_loop
:process_data
:end_section

sed 提供了两个主要的分支命令:

  • b [label] (Branch): 无条件跳转到指定的 label。如果未提供 label,sed 会跳转到脚本的末尾,有效地开始读取新一行的下一个循环(如果还有输入行)。
  • t [label] (Test and Branch): 仅当自上次读取输入行或执行上次 t 命令以来,s///(替换)命令成功进行了替换时,才跳转到 label。如果未提供 label,它会跳转到脚本的末尾。这对于循环直到满足某个条件(没有更多的替换发生)非常有用。

示例:使用循环和分支处理成对的行

Section titled “示例:使用循环和分支处理成对的行”

我们考虑一个 books.txt 文件,其中每本书的书名和作者分别在连续的两行上。我们希望将它们合并到一行,用逗号分隔,然后仅当合并后的行包含 “Paulo” 时,才在行前加上一个连字符(- )。

假设 `books.txt` 文件内容如下:
A Storm of Swords
George R. R. Martin
The Two Towers
J. R. R. Tolkien
The Alchemist
Paulo Coelho
The Fellowship of the Ring
J. R. R. Tolkien
The Pilgrimage
Paulo Coelho
A Game of Thrones
George R. R. Martin

实现此功能的 sed 脚本:

$ sed -n ' \
N; # 将下一行附加到 pattern space,创建 'line1\nline2'
s/\n/, /; # 将换行符替换为 ', '
/Paulo/!b PrintLine; # 如果找不到 'Paulo',则跳转到 PrintLine
s/^/- /; # 如果找到了 'Paulo'(未发生跳转),则在行前添加 '- '
:PrintLine
p # 打印(可能已修改的)pattern space
' books.txt

输出:

A Storm of Swords, George R. R. Martin
The Two Towers, J. R. R. Tolkien
- The Alchemist, Paulo Coelho
The Fellowship of the Ring, J. R. R. Tolkien
- The Pilgrimage, Paulo Coelho
A Game of Thrones, George R. R. Martin
  • sed -n '...' books.txt: -n 选项抑制 sed 的自动打印行为。我们将使用 p 命令显式地打印。
  • N;: 这是 Next 命令。它将下一行输入附加到 pattern space(模式空间)中,并用换行符(\n)分隔。因此,如果 pattern space 原来包含 “Title”,执行 N 后它将包含 “Title\nAuthor”。这个命令隐式地创建了一个处理成对行的循环。
  • s/\n/, /;: substitute(替换)命令将嵌入的换行符(由 N 引入)替换为一个逗号和一个空格。此时 pattern space 变为 “Title, Author”。
  • /Paulo/!b PrintLine;: 这是一个条件分支。/Paulo/ 是一个匹配包含 “Paulo” 的行的地址。! 否定了这个条件,所以它的意思是“如果 pattern space 不包含 ‘Paulo’”。如果条件为真,b PrintLine 命令将分支(跳转)到 :PrintLine 标签。如果条件为假(即 pattern space 确实包含 “Paulo”),则继续执行下一个命令。
  • s/^/- /;: 这个替换命令只有在前一个分支未被执行时才会到达(这意味着找到了 “Paulo”)。它在 pattern space 的开头(^)添加一个连字符和一个空格(- )。
  • :PrintLine: 这定义了 PrintLine 标签。
  • p: print 命令打印当前 pattern space 的内容。

这个脚本演示了如何使用 N 命令一起处理多行,以及如何使用带标签的 b 命令根据模式匹配来控制执行流程。原始示例使用了 h;n;H;x,这是另一种使用 hold space(保持空间)连接行的方式,更适合处理复杂的多行记录。对于连接相邻的行,N 命令通常更简单。

使用保持空间的替代方法(更接近原始示例): 原始示例使用 h;n;H;x 来组合行。这个序列的工作方式如下:

  1. h: 将 pattern space(当前行,例如 “Title”)复制到 hold space。
  2. n: 将下一行(例如 “Author”)读入 pattern space(覆盖 “Title”)。
  3. H: 将 pattern space(“Author”)附加到 hold space。此时 hold space 变为 “Title\nAuthor”。
  4. x: 交换 pattern space 和 hold space。现在 pattern space 包含 “Title\nAuthor”。

然后,脚本将类似地继续:s/\n/, /; /Paulo/!b Print; s/^/- /; :Print; p。

选择使用 N 还是 h;n;H;x 取决于具体的的多行处理任务。N 对于固定的行对更简单,而 hold space 操作对于复杂的场景提供了更大的灵活性。

循环和分支,特别是与 hold space 操作结合使用时,是 sed 最高级的功能之一。它们需要仔细思考,但为复杂的文本转换提供了强大的能力。