Skip to content

Sed 管理模式

除了基本的逐行处理,sed 还提供了强大的命令来操作其内部缓冲区:模式空间 (pattern space) 和暂存空间 (hold space)。理解这些命令(n、N、h、H、g、G、x、p、P、d、D)将释放 sed 进行复杂多行操作、文本重排以及基于前一行进行条件处理的能力。

小写的 n 命令从根本上改变了 sed 对当前行的正常流程。它的动作包括:

  1. 打印模式空间的当前内容(除非默认输出被 -n 选项或之前的 d 命令抑制)。
  2. 清空模式空间。
  3. 将输入流的 下一 行读入当前为空的模式空间。如果没有更多输入行,sed 终止。
  4. 对模式空间中 新读入 的这一行执行脚本中所有后续命令。

语法:

[address1[,address2]]n

示例:仅打印 books.txt 中的偶数行(使用每条目占一行的格式):

$ sed -n 'n;p' books.txt

输出(假设 books.txt 至少有 6 行):

2) The Two Towers, J. R. R. Tolkien, 352
4) The Fellowship of the Ring, J. R. R. Tolkien, 432
6) A Game of Thrones, George R. R. Martin, 864

解释:

  • 第 1 行读入模式空间 (pattern space)。-n 抑制默认打印。
  • n: 模式空间(第 1 行)不 打印(因为有 -n)。下一行(第 2 行)读入模式空间。
  • p: 模式空间(第 2 行)打印。
  • 第 3 行读入模式空间。
  • n: 模式空间(第 3 行)不 打印。下一行(第 4 行)读入模式空间。
  • p: 模式空间(第 4 行)打印。以此类推。

考虑一个脚本:command1; command2; n; command3; command4。command1 和 command2 操作原始行。然后 n 打印它(如果不是 -n),读取下一行,而 command3 和 command4 操作这个 新读入 的行。

暂存空间 (hold space) 是 sed 的另一个缓冲区,它在读取-执行-显示循环之间持久存在。对于需要记住前一行的任务,它至关重要。sed 命令不能直接操作暂存空间;必须先将数据移动到模式空间。对于以下示例,假设 books_multiline.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

x 命令交换模式空间和暂存空间的内容。

示例:仅打印 books_multiline.txt 中的作者姓名。

$ sed -n 'x;n;p' books_multiline.txt

输出:

George R. R. Martin
J. R. R. Tolkien
Paulo Coelho
J. R. R. Tolkien
Paulo Coelho
George R. R. Martin

前两行 (Title1, Author1) 的工作流程:

  1. 第 1 行 (Title1) 读入模式空间 (PS)。PS={Title1},HS={初始为空或包含前一个循环的内容}。
  2. x: 交换。PS={HS 内容},HS={Title1}。
  3. n: (由于 -n 不打印)。读取第 2 行 (Author1) 到 PS。PS={Author1},HS={Title1}。
  4. p: 打印 PS,即 Author1。
  5. 重复此过程。对于下一对,HS 最初将包含 Author1,x 后则包含 Title2。

h: 将模式空间的内容复制到暂存空间,覆盖暂存空间的原有内容。

H: 将模式空间的内容附加到暂存空间(附加前会先添加一个换行符)。

语法:[地址1[,地址2]]h 和 [地址1[,地址2]]H

示例:仅打印作者为 “Paulo Coelho” 的书名。

$ sed -n '/Paulo Coelho/!h; /Paulo Coelho/{x;p;}' books_multiline.txt

输出:

The Alchemist
The Pilgrimage

解释:

  • 读取书名行(例如,“The Alchemist”)时,它 不 匹配 /Paulo Coelho/。因此,h 将其复制到暂存空间 (hold space)。HS= {“The Alchemist”}。
  • 读取对应的作者行(“Paulo Coelho”)时,它匹配 /Paulo Coelho/。跳过 !h。
  • 执行块 {x;p;}:x 交换模式空间 (“Paulo Coelho”) 和暂存空间 (“The Alchemist”)。现在 PS= {“The Alchemist”},HS= {“Paulo Coelho”}。
  • p 打印模式空间内容,即书名 “The Alchemist”。

示例:打印作者为 “Paulo Coelho” 的书名及其作者。

$ sed -n '/Paulo Coelho/!h; /Paulo Coelho/{H;x;p;}' books_multiline.txt

输出:

The Alchemist
Paulo Coelho
The Pilgrimage
Paulo Coelho

解释(针对 “The Alchemist” 和 “Paulo Coelho”):

  • “The Alchemist” (书名):h 将其复制到 HS。HS= {“The Alchemist”}。
  • “Paulo Coelho” (作者):匹配。H 将 PS 附加到 HS。HS= {“The Alchemist\nPaulo Coelho”}。
  • x 交换。PS= {“The Alchemist\nPaulo Coelho”},HS= {“Paulo Coelho”}。
  • p 打印 PS。

g: 将暂存空间的内容复制到模式空间,覆盖模式空间的原有内容。

G: 将暂存空间的内容附加到模式空间(附加前会先添加一个换行符)。

语法:[地址1[,地址2]]g 和 [地址1[,地址2]]G

示例:打印作者 “Paulo Coelho”,然后打印对应的书名。

$ sed -n '/Paulo Coelho/!h; /Paulo Coelho/{p;g;p;}' books_multiline.txt

输出:

Paulo Coelho
The Alchemist
Paulo Coelho
The Pilgrimage

解释(针对 “The Alchemist” 和 “Paulo Coelho”):

  • “The Alchemist” (书名):h 将其复制到 HS。HS= {“The Alchemist”}。
  • “Paulo Coelho” (作者):匹配。p 打印 PS (“Paulo Coelho”)。
  • g 将 HS 复制到 PS。PS= {“The Alchemist”}。
  • p 打印 PS (“The Alchemist”)。

示例使用 G:打印作者 “Paulo Coelho”,然后打印对应的书名(结果相同)。

$ sed -n '/Paulo Coelho/!h; /Paulo Coelho/{G;p;}' books_multiline.txt

解释(针对 “The Alchemist” 和 “Paulo Coelho”):

  • “The Alchemist” (书名):h 将其复制到 HS。HS= {“The Alchemist”}。
  • “Paulo Coelho” (作者):匹配。G 将 HS 附加到 PS。PS= {“Paulo Coelho\nThe Alchemist”}。
  • p 打印 PS。

这些命令是进行复杂文本操作的基础构建块,例如反转段落中的行序、处理由特定模式界定的文本块,或从多行收集信息以生成摘要。例如,可以收集所有以 ‘Error:’ 开头的行,将它们存储在暂存空间中,然后在文件处理结束时一次性打印出来。

  • 忘记 -n: 如果你手动使用 p 或 P 控制打印,请记住使用 -n 来抑制自动打印,否则你会得到重复的行。
  • H 和 G 命令中的换行符: 这些命令在附加前会插入一个换行符。这通常很有用,但在后续处理组合行时需要考虑。
  • 复杂性: 尽管功能强大,但涉及模式空间/暂存空间的复杂嵌套逻辑可能会变得难以调试。对于非常复杂的转换,awk 或像 Python、Perl 这样的脚本语言可能更清晰。
  • 请查阅官方的 GNU sed 手册以获取每个命令的权威详细信息和更多示例。