Skip to content

Sed 分支

SED 脚本可以使用分支命令做出决策并跳转到脚本的不同部分。这允许更复杂的文本处理逻辑,类似于其他编程语言中的 goto 语句。主要的分支命令是 b、t 和 T。

b 命令无条件地跳转到一个标签(label)。如果没有提供标签,它会跳转到脚本的末尾,有效地跳过当前循环中所有后续命令。

t label 命令 仅在 自读取上一行输入或执行上一条 t 或 T 命令以来,有一个 s///(替换)命令成功执行了替换操作时,才跳转到指定的 label。如果没有提供 label,它会跳转到脚本的末尾。这对于仅在发生更改时执行某些操作非常有用。

让我们看一个简单的例子。假设我们想通过在包含 “Martin” 的行前加上 “AUTHOR_UPDATE: ” 来标记这些行,但前提是成功地将 “Martin” 替换为 “M.A.R.T.I.N”。我们将使用标准的 books.txt 文件:

文件: `books.txt`
1) A Storm of Swords, George R. R. Martin, 1216
2) The Two Towers, J. R. R. Tolkien, 352
3) The Alchemist, Paulo Coelho, 197
4) The Fellowship of the Ring, J. R. R. Tolkien, 432
5) The Pilgrimage, Paulo Coelho, 288
6) A Game of Thrones, George R. R. Martin, 864
[jerry]$ sed '
/Martin/ {
s/Martin/M.A.R.T.I.N/
t was_substituted
}
b # Skip if no substitution or not Martin
:was_substituted
s/^/AUTHOR_UPDATE: /
' books.txt

执行此命令会得到:

AUTHOR_UPDATE: 1) A Storm of Swords, George R. R. M.A.R.T.I.N, 1216
2) The Two Towers, J. R. R. Tolkien, 352
3) The Alchemist, Paulo Coelho, 197
4) The Fellowship of the Ring, J. R. R. Tolkien, 432
5) The Pilgrimage, Paulo Coelho, 288
AUTHOR_UPDATE: 6) A Game of Thrones, George R. R. M.A.R.T.I.N, 864

在这里,如果 s/Martin/M.A.R.T.I.N/ 成功,t was_substituted 会跳转到标签 :was_substituted,并执行 s/^/AUTHOR_UPDATE: /。否则,t 命令不会做任何事情,并且会触发 b(对于当前行,无条件分支到脚本末尾),或者如果 b 不存在,执行将继续到标签命令(本身不做任何事情),然后是前缀替换,这对于不包含 “Martin” 的行来说不是我们想要的。当前的逻辑确保只有执行了替换的行才会被加上前缀。

t 命令在创建循环方面非常强大。考虑一个例子,我们希望在包含 “Paulo” 的行前最多添加四个连字符。这个例子首先涉及连接行,所以我们将使用一个稍微不同的输入文件 books_split.txt,其中书名和作者位于不同的行:

文件: `books_split.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

该命令使用 h;N;H;x 来连接成对的行,然后使用 s/\n/, / 将嵌入的换行符替换为逗号和空格。之后,它进入一个针对包含 “Paulo” 的行的循环。

[jerry]$ sed -n '
h;N;H;x
s/\n/, /
:Loop
/Paulo/s/^/-/;
/----/!t Loop
p
' books_split.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

让我们分解一下循环部分:

  1. h;N;H;x: 这些命令一起工作。h 将第一行(例如,书名)复制到保持空间(hold space)。N 将下一行(例如,作者)附加到模式空间(pattern space),并用嵌入的换行符分隔(例如 “Title\nAuthor”)。H 将模式空间的内容(现在是 “Title\nAuthor”)附加到保持空间(保持空间现在是 “Line 1 \n Line 2 \n Title\nAuthor” - 在此示例中,Line 1和Line 2是之前循环处理的行)。x 交换模式空间和保持空间的内容。原始教程中的 h;n;H;x; 是另一种常见的连接行方式,效果与此处的 h;N;H;x 不同。这里使用的 h;N;H;x 是另一种连接两行的常见用法,它首先将当前行存入hold space,然后读取下一行到pattern space,接着将pattern space内容附加到hold space,最后交换两者。一种更简单的连接两行的方法通常是 N ; s/\n/, /,如果紧接着打印的话。
  2. s/\n/, /: 将模式空间中的换行符替换为逗号和空格。
  3. :Loop: 定义一个名为 Loop 的标签。
  4. /Paulo/s/^/-/: 检查(连接后的)行是否包含 "Paulo"。如果是,它会在行首添加一个连字符 (-)。这个 s///命令决定了t` 是否会分支。
  5. /----/!t Loop: 这是条件分支。!t Loop 的意思是:如果模式空间 不 匹配 /----/(即,不以四个连字符开头),并且 前面的 s/// 命令(/Paulo/s/^/-/) 成功执行了替换,则跳转回 Loop 标签。如果 s/// 未成功(例如行中没有 “Paulo”),则 t 不会分支。
  6. p: 打印模式空间中(可能已修改的)行。-n 选项抑制默认输出,因此此处需要 p 命令。 这个过程会重复执行,每次向包含 “Paulo” 的行添加一个连字符,直到添加了四个连字符或未找到 “Paulo”。

为了提高可读性,SED 命令可以写在脚本文件中的单独行上,或者使用 -e 选项为每个命令指定。对于单行命令,可以使用分号(尽管如上所示,换行符通常更清晰):

[jerry]$ sed -n 'h;N;H;x; s/\n/, /; :Loop; /Paulo/s/^/-/; /----/!t Loop; p' books_split.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

T 命令 (失败时条件分支 - GNU 扩展)

Section titled “T 命令 (失败时条件分支 - GNU 扩展)”

T label 命令是 GNU 扩展。它与 t 相反:它 仅在 上一个 s/// 命令 未能 执行替换时才跳转到 label。如果没有提供 label,它会跳转到脚本的末尾。这对于当未找到特定模式并进行替换时执行操作非常有用。

理解分支命令能够解锁 SED 中更高级的脚本编写能力,从而基于数据中的条件实现复杂的文本转换。