sed与awk-第四章-编写sed脚本
第四章 编写sed脚本
编写sed脚本
sed脚本就是一组编辑命令的集合,运行在输入文件上实现批量文本修改。
手动编辑器 vs sed脚本
- vi交互式编辑器:敲一条命令立刻看到效果,支持undo撤销,操作安全可控。
- sed脚本:自动化批量编辑,没有撤销undo机制,写错脚本会产生非预期结果。
编写sed脚本三步(和第三章写正则的方法论完全一致)
- 先搞清楚你要做什么处理。
- 清晰描述完整处理流程。
- 反复测试脚本之后,再执行最终修改。
测试最佳实践:准备多份样本输入,观察输出结果是否符合预期。
sed三大基础原理(重中之重)
- 脚本里面所有编辑命令,会依次应用到每一行输入。
- 默认命令作用于全部行;只有行寻址可以限制命令生效的行。
- 原始输入文件不会被修改;sed修改的是行的副本,副本输出到标准输出(屏幕)。
后续第五章、第六章会讲解更多sed命令。本章重点理解脚本的运行逻辑,而不是死记全部命令。
在脚本中应用命令
非常容易踩坑:很多初学者误以为:sed执行完一条命令,把这条命令作用全部行,再执行下一条命令。 真实流程:读取一行 → 将脚本全部命令依次执行在这一行 → 输出 → 读取下一行。
也就是说:后面的命令,处理的是前面命令修改之后的模式空间内容,不是原始输入行。旧的原始行会被丢弃。
坑示例
脚本,本意:pig→cow;cow→horse
输入行:pig
- 第一条:pig替换成cow
- 第二条:把刚刚生成的cow又替换成horse
输出结果:horse
对错取决于业务想不想要 “衍生出来的内容被后续命令再次处理”。 问题根源:前一条命令修改了文本,后一条命令继续处理修改后的结果。 解决方案:调换命令顺序
模式空间
模式空间(pattern space):sed内部的临时缓冲区、工作区。每次存放一行输入的副本,所有编辑命令全部在这里做修改。
特点:
- 每次读入一行文本的副本放入模式空间。原始文件保持不变。
- 脚本的每一条编辑命令,依次修改模式空间里面这份副本。
- 全部命令执行完毕,输出模式空间内容;清空,读取下一行。
- 处理超大文件也不怕,因为一次只加载一行,不会占满内存。
flowchart TD
A["The UNIX System<br/>输入"] --> B["模式空间"]
C["脚本<br/>s/Unix/UNIX/<br/>s/UNIX System/UNIX Operating System/"] -.-> B
subgraph 模式空间
B1["The Unix System"]
B2["The UNIX System"]
B3["The UNIX Operating System"]
B1 -->|执行第1条替换| B2
B2 -->|执行第2条替换| B3
end
B --> D["输出<br/>The UNIX Operating System"]
图4‑1 脚本命令改变模式空间内容
关键点:第二个替换匹配的是修改之后的行,不是原始输入。
补充:sed还有另外一块缓冲区叫保持空间 hold space,第六章高级sed命令讲解。
寻址上的全局透视
sed默认是全局模式,不加地址,命令对每一行生效。 vi/ex编辑器默认只作用当前行;sed正好反过来:默认全部行,需要写地址来限制范围。
sed命令可以指定:0个、1个、2个地址。地址可以是:行号、正则表达式(斜杠包裹/pattern/)。
| 地址写法 | 行为说明 |
|---|---|
| 无地址 | 命令对所有行生效 |
| 1个地址 | 只匹配这个地址的行执行命令;地址可以是行号1或者正则/Sebastopol/ |
| 地址1,地址2 | 范围:从匹配地址1的行开始,一直到匹配地址2的行(包含两端) |
| 地址! | 感叹号!,取反;命令作用于所有不匹配该地址的行 |
示例演示(d删除命令)
- 无地址,删除全部行,无输出
|
|
- 只删除第1行
|
|
- 删除文件最后一行;
$代表输入流最后一行,和正则行尾$不是同一个东西
|
|
- 删除全部空行,正则作为地址
|
|
- 删除
.TS到.TE之间全部行(tbl表格块)
|
|
- 删除50行直到文件末尾
|
|
- 混合行号+模式:删除第1行直到第一个空行(删除邮件头部)
|
|
重要细节:范围地址一旦匹配第一个地址,就持续应用命令,直到命中第二个地址;如果文件从头到尾没有匹配第二个地址,则会删除从第一个匹配之后的全部行。
地址+!取反示例
保留.TS~.TE块,其余全部删除(提取表格块)
|
|
翻译:不在这个范围里面的行,全部删除。
分组命令 { }
大括号{}实现两件事:
- 在同一个地址/行范围上,执行多条sed命令。
- 地址嵌套:在一个行范围内部,再增加一层过滤条件。
语法硬性规则:
- 左大括号
{必须在行末尾- 右大括号
}必须单独占一行,后面不能有空格
示例1:在.TS和.TE表格块内部,删除块里面的空行(嵌套地址)
示例2:同一个范围,执行多条修改命令
测试并保存输出
sed只会修改副本,原始文件不受影响。
想要保存结果,必须使用重定向>输出到新文件:
|
|
❗严重坑点:绝对不能重定向输出到输入源文件
> testfile,会直接清空破坏原文件!
测试工作流
- sed输出重定向到新文件 newfile
- 使用diff对比原始文件和输出文件,检查改动是否正确
|
|
- 确认没问题,备份原文件,再覆盖原文件。
两个辅助shell脚本(原书提供)
testsed:用于测试sed脚本
把输出写入临时文件 tmp.文件名,方便人工检查;不会修改原始文件。
for x等价于for x in "$@",$@代表脚本命令行上传入的所有参数。x:代表变量名字本身,$x:代表取出变量 x 里面存的值。
使用:写好sed脚本sedscr,执行testsed 文件名,检查tmp.*临时文件。如果脚本写错,修改sedscr,重新在原始文件上跑,不要去修改临时文件。
runsed:确认脚本没问题后,用来永久修改原始文件
内部逻辑:生成临时文件;校验临时文件不为空;自动备份原文件
xxx.bak,再覆盖原文件。 使用前强烈建议先用testsed完成测试!
|
|
用法示例:处理ch0开头的一批文件
|
|
sed脚本的4种类型
1. 对同一文件的多重编辑
场景:一个文件做批量多处修改。 开发技巧:一条命令、一次测试;迭代叠加,不要一次性写一大堆命令再测试。
案例:把打印格式化文件转为troff格式,需求清单
- 将空行替换段落宏
.LP - 删除以
+开头的下划线行 - 删除每行开头前导空格
- 多个连续空格压缩成单个空格
- 处理句号后面的多空格,保留句号后面两个空格
完整脚本 sedscr
运行测试
|
|
开发思路:写一条 → 测试;再加下一条,逐步迭代。出现bug,就逐个注释命令定位问题。
2. 改变一组文件
场景:上百个文件批量做搜索替换。
编写替换列表脚本,先用testsed抽样测试,确认没问题,再用runsed批量修改。
示例片段
实战建议:先用grep扫描原始文本,摸清真实文本的各种情况;挑选有代表性测试样例,不要只拿一两个简单样例就直接跑全部文件。
3. 提取文件的内容
sed
-n参数:关闭默认输出;只打印p命令显式指定的行。 优势:可以提取行范围;grep只能匹配单行,不能直接提取一段连续多行。
示例A:提取troff宏定义
.deBL 开始,..结束,提取整个宏定义。
|
|
封装shell脚本 getmac,传入宏名字作为参数
调用:
|
|
扩展版本支持多个宏包(-ms / -mm /‑man),使用case分支。
示例B:生成文档提纲 do.outline
读取标题宏,替换、缩进输出文档大纲,不改动原文件,仅输出到屏幕。
4. 编辑工作转移(管道流式处理,不落地写文件)
场景:sed放在管道中间,处理数据直接交给下一个程序,不修改磁盘上原始文件,不生成中间文件。
示例:把双连字符--转换成troff长破折号\(em;排除3个及以上连续短横线(防止把绘制横线也转换)。
|
|
地址! 解读:行如果匹配
---,就不执行替换;其余全部行执行替换。
放到管道直接交给troff格式化,无中间文件:
|
|
典型shell脚本
format,封装sed、eqn、pic、troff整套排版流水线。
编写sed脚本提示
- 充分了解你的输入:使用grep提前探查原始文本,知道会出现哪些格式。
- 先小样本测试;采样测试;既要测试应该匹配的,也要测试不该匹配的边界情况。
- 逐步开发,每新增一条命令就要测试。
- 不必追求100%处理所有极端边角案例;极少出现的异常情况,可以留给人工处理。
本章核心速记卡片
- sed执行顺序:读一行 → 全部脚本命令依次跑在模式空间副本 →输出;前序修改会影响后续命令。
- 模式空间:每行副本的临时缓冲区;原始文件永远不会被自动修改。
- 寻址:0/1/2个地址;逗号表示范围;
!取反;范围地址要警惕第二个模式永远匹配不到的风险。 - 分组
{}:对同一个范围执行多条命令;语法严格,{在行尾,}独占一行。 - 测试流程:输出重定向新文件 → diff对比;testsed做测试;确认无误再runsed永久修改。禁止>直接定向输入文件。
- sed‑n:关闭默认输出,配合p打印需要的行,可以提取多行片段。
- 4类脚本模型:单文件多重编辑;批量多文件修改;内容提取;管道流式处理。
文章作者 会写代码的小郎中
上次更新 2011-08-03
许可协议 CC BY-NC-ND 4.0