sed与awk-第六章-高级sed命令
第六章高级sed命令
本章的高级sed命令分为三大类:
- 多行模式空间命令:
N、D、P(大写) - 保持空间缓冲区命令:
H/h、G/g、x - 流程控制(分支与条件):
:标签、b无条件分支、t条件测试分支
sed默认正常执行流程: 读一行输入 → 放入模式空间,从上到下依次执行脚本所有命令 → 脚本执行完毕,输出模式空间内容,清空模式空间,读取下一行,回到脚本开头。 本章所有高级命令都会打破这套默认流程,这也是难理解的根源。
一、多行模式空间
sed默认一次处理一行;多行命令允许模式空间存放多行文本,实现跨行匹配。
小写基础命令和大写多行命令对照
基础单行命令 多行版本 作用 nN读取下一行 dD删除 pP打印
1. 追加下一行:N(大写Next)
N:读取下一行输入,追加到当前模式空间末尾,两行之间嵌入换行符\n;不会输出原有内容,继续向下执行后续脚本。
重点区分:
n(小写next):输出当前模式空间,清空,读入下一行;不会产生多行模式空间N(大写Next):不输出,直接把下一行拼进模式空间,产生多行,行之间用\n分隔。
多行模式空间中元字符行为:
^:只匹配模式空间最开头,不匹配内部\n之后的行首$:只匹配模式空间最末尾,不匹配内部\n之前的行尾- 内部换行符用
\n进行匹配
示例场景:短语被拆分成两行
原始文本片段
需求:跨行把Owner and Operator Guide替换成Installation Guide
基础脚本:匹配行尾是Operator的行,读入下一行,匹配换行符
坑:替换匹配了
\n,但没有输出换行,行会拼接成超长一行。 sed的替换字符串中不能直接写\n插入换行,必须反斜杠转义后回车换行。
- 第一行末尾
Installation Guide\→ 反斜杠,然后敲回车键,这样可以插入换行符 - 第二行只有一个
/,是 sed 替换命令的结束分隔符
修复版本,保留换行:
重要避坑:$!N
如果文件最后一行匹配上地址,执行N,但已经没有下一行可读,sed直接退出,本行都不会输出!
安全写法:$!N,只有不是最后一行的时候,才执行N。含义:$代表最后一行;!取反;不是最后一行,才执行 N
另外一个坑:如果目标短语本身就在同一行,不要贸然执行N,否则会把下一行无关内容拉进模式空间,破坏文本。解决思路先做一次单行的替换;只有单行匹配失败,再去执行 N 读取下一行
2. 多行删除 D(大写Delete)
d(小写):清空整个模式空间,读新行,跳回脚本开头。D(大写):找到模式空间里「第一个\n换行符」,把换行符 以及它前面所有字符全部删掉 ;换行后面剩下的内容留在模式空间,不读新行,直接跳回脚本开头重新跑脚本。
案例:把连续多个空行压缩为最多一个空行。
错误写法(用d)
缺陷:偶数个连续空行会全部删掉,奇数才保留一行,效果不符合预期。
正确写法(D多行删除)
逻辑:遇到空行,N读下一行;如果两行都是空\n,D删掉第一行空行;回到脚本顶端继续判断剩下那行空行。最终多个连续空行只保留1行。
3. 多行打印 P(大写Print)
p:打印模式空间全部内容P:只打印模式空间中第一个嵌入换行符\n之前的第一部分。
使用前提:一般配合sed
-n(关闭默认输出),或者脚本流程走不到脚本末尾(不会自动输出)的时候使用。
经典组合循环:N → s替换 → P → D
N把下一行读进模式空间,变成两行- 做跨行替换
P输出模式空间的第一行D删掉第一行,剩下第二行留在模式空间,回到脚本顶端
效果:第二行保留在缓冲区,可以继续参与后续所有规则处理,实现滚动两行窗口。
示例:跨行匹配UNIX\nSystem替换
测试文本
sed脚本
&不是代表下一行,是代表本次正则匹配捕获到的那一串字符既/\nSystem/
s// Operating &/,如果s的第一个//中间什么都不写:复用最近一次成功匹配的正则表达式。上一条命令是/\nSystem/,刚刚匹配成功,匹配到的文本就是\nSystem。等价完整写法:s/\nSystem/ Operating &/
学习案例:Scribe字体标签 @fl(...)跨行转换
需求:把scribe标记@fl(内容)转troff粗体\fB内容\fR;标记可能在同一行多次出现,也可能括号跨两行。
原始简单版本缺陷:
修正单行匹配,用[^)]*匹配除右括号以外字符(非贪婪效果)
|
|
但是上面只处理单行,处理不了跨两行的@fl(xxx\nxxx)。
使用 N + P + D 循环处理跨行场景:
逻辑:
- 先处理本行能完成的匹配
- 如果行内出现
@fl(但是本行没有),执行N读下一行 - 跨行替换;
P输出第一行;D删除第一行,剩下第二行回到脚本开头继续解析是否还存在标记。 [^字符]正则语法:脱字符^写在方括号里面第一位,代表取反:匹配【不是这个字符】的任意字符。
缺陷:只能最多跨两行,如果括号跨3行以上,这套脚本失效。
实战示例:转换Interleaf文档
Interleaf标记:<para>代表段落,后面跟空行;<Figure Begin>是图片二进制数据。
需求:
- 将
<para>+后面空行替换troff段落宏.LP - 把图片块导出到单独文件
fig.interleaf,原文位置替换图片占位宏.FG ... .FE - 删除多余空行
sed脚本:
说明:
- 匹配
<para>,N读取后面空行进入模式空间;c命令改写整个模式空间内容为.LP - 范围匹配图片块,
w把区间全部行写入外部文件;匹配到结束标记时i插入占位宏;d删除模式空间,不在原文输出图片二进制。
二、保持空间 hold space
sed有两块缓冲区:
- 模式空间(pattern space):正在处理的文本,日常sed命令操作的就是它
- 保持空间(hold space):临时备份存储区;普通s/d/p等命令不能直接操作保持空间,只能通过h H g G x互相拷贝交换
| 命令 | 含义 |
|---|---|
h |
覆盖:把模式空间内容复制,覆盖重写保持空间原有内容 |
H |
追加:模式空间内容追加到保持空间末尾;追加前自动加换行符\n |
g |
覆盖:保持空间复制,覆盖重写模式空间 |
G |
追加:保持空间内容追加到模式空间末尾;追加前自动加换行符\n |
x |
交换:模式空间 和 保持空间两者完整互换内容 |
大写H/G:追加;小写h/g:覆盖重写;x直接交换两块缓冲区。
案例1:调换成对行顺序
输入文件
需求:把1开头行放到2开头行后面输出
逻辑:
- 匹配
1开头行:h复制到保持空间;d删除模式空间,不输出,回到脚本开头读下一行 - 读到
2开头行,G把保持空间的1开头行追加到模式空间后面;脚本走到末尾自动输出两行。
漏洞:如果1开头的后面没有紧跟着2开头,那一行会永久消失,没有输出。
案例2:只修改行中一部分文本(局部大写转换)
需求:句子find the Match statement → find the MATCH statement;只把中间语句名大写,其余文本原样保留。
思路:
h:把完整原始行备份进保持空间- 模式空间只提取中间语句名字
y做大小写转换(y会转换整个模式空间)G把原始完整行追加回来- 替换重组,把大写后的名字放回原句子中
脚本:
案例3:收集文本块,增加HTML段落标签<p></p>
输入:段落由多行组成,段落之间用空行分隔。
需求:遇到空行的时候,把之前收集的所有行用<p>...</p>包裹输出。
脚本主体
流程拆解:
- 读到非空行:
H追加到保持空间,d删除模式空间,回到开头读下一行。文本全部积攒在hold space,完全不输出。 - 读到空行:
x交换模式空间、保持空间;此时模式空间是积攒的段落文本,保持空间存那个空行s/^\n/<p>/:H命令会在内容开头自带换行,替换开头换行符为<p>s/$/<\/p>/:模式空间末尾加上</p>G把保持空间存的原始空行追加回来;脚本末尾自动输出带标签段落。
漏洞:文件最后一段末尾没有空行的话,最后一段永远不会输出!
修复,专门匹配文件最后一行$:
逻辑:匹配文件最后一行,如果不为空,H存入保持空间;把模式空间清空变成空行,就会触发上面空行的处理逻辑输出段落。
案例4:索引条目脚本 index.edit
核心用途:正则元字符[ ] \ * .需要做两层转义:一层用于sed匹配模式;一层用于替换字符串。
实现技巧:
h原始条目存入保持空间- 模式空间做模式侧元字符转义
x交换;模式空间做替换侧元字符转义- 再次交换,拼接完整sed替换脚本输出。
核心知识点:保持空间用来保存两份不同处理版本的同一行,交替交换修改。
正则与替换侧元字符转义表
| 特殊字符 | 正则匹配侧(s/左边/) |
替换字符串侧(s/右边/) |
|---|---|---|
\ |
转义符,自身要写 \\ |
转义符,自身要写 \\ |
. |
匹配任意字符 → 转义 \. |
普通字符,不用转义 |
* |
前项重复 → 转义 \* |
普通字符,不用转义 |
[ ] |
字符集 → 转义 \[ \] |
普通字符,不用转义 |
^ |
行首锚点 → \^ |
普通字符,不用转义 |
$ |
行尾锚点 → \$ |
普通字符,不用转义 |
& |
普通字符,不用转义 | 整个匹配内容 → 转义 \& |
\( \) |
捕获组(存入 \1~\9) |
普通字符,不用转义 |
\1~\9 |
反向引用(匹配重复文本) | 引用第 n 个捕获组内容 |
\n |
换行符(需配 N) |
换行符 |
匹配侧是正则世界,元字符都要转义;替换侧是字面世界,只有 \、&、\1~\9 特殊,其余全是普通字符。
三、高级流控制:标签、b分支、t测试
sed默认从上往下顺序执行脚本。使用标签、b、t可以实现跳转、循环、条件分支。
语法
- 标签定义
:label
|
|
标签行末尾空格会被当成标签名字一部分!
- 无条件分支
b [label]
|
|
- 如果写标签:跳转到对应标签行继续往下执行
- 不写标签:直接跳到脚本末尾,结束当前行所有处理,不再执行后面任何命令
利用b实现跳过部分脚本逻辑:
b可以写循环:
条件测试分支 t [label]
t:只有本行有任意s替换执行成功,才发生跳转;否则继续向下执行。
|
|
- 无标签:替换成功就跳到脚本末尾。
典型场景:多条件互斥匹配,类似case语句,匹配成功就不再尝试后续规则。
示例:匹配.Rh 0行,优先3个引号参数,其次2个,其次1个。一旦某一条s替换成功,t直接跳出,不再尝试后面的替换。
综合改进:支持跨多行的Scribe字体转换(b+t+标签循环)
原来脚本只能跨两行;用标签循环,不断N读入行,可以处理跨3行甚至更多行括号。
逻辑拆解
:begin循环起点- 只要本行能匹配完整
@fl(...)做替换,b begin跳回开头,继续检测本行剩余其他标记(一行多个标记) - 如果发现
@fl(但是本行没有),执行N读取下一行,尝试跨行替换 t again:跨行替换成功,跳到again标签执行P、D输出第一行;- 如果替换失败(括号还在更下一行),
b begin回到循环开头,继续N读下一行; :again:P打印第一行,D删除第一行,剩余内容回到脚本开头继续解析。
完整综合示例 phrase shell脚本
模拟grep,搜索可能跨行的短语,使用:N h G x b D几乎全部高级sed命令。
逻辑要点:
- 优先判断短语是否完整在当前一行;匹配就b跳到脚本末尾直接输出。
- 不匹配:
N读下一行,h保存两行完整副本进保持空间。 - 删掉模式空间第一行,只看第二行,如果第二行命中关键词,直接输出。
- 恢复保持空间原始两行,把换行替换成空格,把两行拼接成一整行文本,测试是否跨行匹配短语。
- 匹配成功,恢复原始换行版本输出;失败:D删除第一行,保留第二行,回到脚本开头继续循环。
本章全部核心知识点速记(复习清单)
1)多行模式空间
N:追加下一行到模式空间,生成多行;注意最后一行$!N的坑;多行中^ $只针对整个模式空间首尾,内部换行用\n匹配。D:删除模式空间第一行,不读新行,回到脚本开头;d清空全部模式空间读新行。压缩多空行用D。P:打印模式空间第一行;经典循环组合:N → s → P → D实现两行滚动窗口。
2)保持空间 hold space
- 两套缓冲区:模式空间(主操作)、保持空间(临时备份)
h/H:模式空间→保持空间(覆盖/追加)g/G:保持空间→模式空间(覆盖/追加)x:交换两块缓冲区。- 用途:保存原始文本副本,只修改部分内容;逐行收集文本块。
- 坑:
H/G追加时会自动插入换行符。
3)流程控制
:tag定义标签;b label无条件跳转,不带标签跳到脚本结尾。可实现循环,跳过部分脚本。t label条件跳转:只要有任意s替换成功才跳转,适合case式互斥匹配。- 三者组合可以写循环,处理跨任意多行的匹配。
sed高级脚本阅读困难的根源:
D会回到脚本开头,不会读新行;b/t跳转;两块缓冲区互相拷贝;模式空间可以存放多行。
文章作者 会写代码的小郎中
上次更新 2011-08-05
许可协议 CC BY-NC-ND 4.0