第五章 基本 sed 命令

sed 命令的语法

sed通用语法:

  1. 大部分命令:可以接受0个、1个、2个地址(行号/正则模式),支持行范围
1
[address] command
  1. 少数命令只接受单个行地址,不能用于行范围
1
[line‑address] command
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 0地址,删除所有行,所有行生效
d          

# 删除第2行(banana)
2d         

# 删除包含cat的行
/cat/d     

# 删除2~4行 banana cat dog
2,4d       

# 从匹配banana的行开始,到匹配dog的行结束,整块删除
/^banana/,/^dog/d

命令分组大括号 { }

对同一个地址,执行多条命令;支持多层嵌套分组。

1
2
3
4
5
address{ 
command1 
command2 
command3 
}

规则:

  • 左大括号 { 放在地址行末尾;
  • 右大括号 } 必须独占一整行;
  • 大括号内部命令行开头可以加空格、制表符做缩进;
  • 大括号内每一条命令还可以自带自己的地址,可以多层嵌套。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 规则:左{跟在地址末尾;右}必须独占一行;内部可以缩进;内部命令可自带地址,支持嵌套

# 同一个地址,执行多条命令
 /^banana/{
     =                   # 打印匹配行的行号
     s/banana/BANANA/    # 替换文本
 }

# 外层是行范围地址,大括号内多条命令
 2,4{
     y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
 }

# 外层范围,内部命令自带独立过滤地址
 2,5{
     /^dog/s/dog/DOG/
 }

# 多层嵌套大括号
2,5{
    /^cat/,/^dog/{
        s/[a-z]/\U&/g
    }
}

同一行写多条命令(分号;)

不推荐日常写脚本,可读性差。

1
n;d

坑:n ; d n后面加空格会报语法错误;d前面允许空格。

注意:a i c(追加、插入、更改)不能用分号放同一行,必须多行书写。

注释

sed脚本注释行以 # 开头。

  1. System V sed限制:注释只能写在脚本第一行;GNU sed可以脚本任意位置写注释。本书示例遵循兼容写法,注释放第一行。
1
# wstar.sed: conver WordStar files
  1. 特殊:脚本文件开头 #n>

如果#后面紧跟字符n(必须是文件前两个字符),等价于命令行参数 -n,关闭sed默认输出,后面内容依旧作为注释。

1
#n 脚本,关闭自动输出

替换

替换命令完整语法

1
[address]s/pattern/replacement/flags

flags标志

标志 作用
数字n 只替换一行中第n次匹配,n取值1‑512;默认1(只替换第一次)
g 全局,一行内全部匹配项都替换
p 替换成功后,打印模式空间内容;只有替换成功才打印
w file 替换成功后,把模式空间写入指定文件file;替换成功才写文件

重点:p、w标志和独立p/w命令区别:标志只在替换成功才执行动作;独立p命令无条件执行。多个替换作用同一行,带p标志会多次打印该行。

替换分隔符可以自定义

默认用/做分隔符;如果模式里面大量出现斜杠/,可以换成别的字符(! #等,不能换行)。

规则:分隔符一共出现3处;如果分隔符本身出现在模式/替换文本中,需要\转义。

1
2
# 把 /usr/mail 替换成 /usr2/mail,改用 ! 作为分隔符
s!/usr/mail!/usr2/mail!

replacement(替换部分)特殊元字符

注意:正则元字符(. * [])在替换域失去特殊含义;只有下面3类符号在替换域有特殊作用。

  1. & 代表整个正则匹配到的全部内容
  2. \1 \2 … \9 回调正则中\( \)捕获的分组子串,最多9组
  3. \ 反斜杠:转义&、分隔符、反斜杠本身;还可以转义换行,实现多行替换文本。

示例:

1
2
3
4
# & 引用全部匹配内容,给UNIX加上troff字体控制
s/UNIX/\\s-2&\\s0/g
# 捕获两组,交换顺序
s/\(.*\):\(.*\)/\2:\1/

数字n标志示例

把一行中第二个制表符替换成>

1
s/· />/2

·代表制表符tab。

替换元字符

替换部分(s/xxx/这里是替换字符串/g):这一块不是正则!绝大多数正则元字符在这里失效,只有少数几个符号还有特殊魔力,这少数几个就叫 替换元字符。

替换元字符 作用
& 代表:正则匹配到的完整全部内容
\1 \2 …\9 捕获分组回调,取\( \)括号里捕获到的子内容
\ 反斜杠 转义:转义 &、反斜杠本身、分隔符;还可以换行实现多行替换

1. & 使用示例

输入:on the UNIX Operating System.

1
s/UNIX/\\s-2&\\s0/g

拆解例子:

正则部分:UNIX 简单字面匹配,找到单词 UNIX。

这里正则匹配到的完整内容就是字符串:UNIX

替换文本:\\s-2&\\s0
这里面 & 就是替换元字符,& = 刚刚匹配到的 UNIX

所以替换字符串 \\s-2&\\s0 展开:

  • \\s-2 → \s-2
  • & → UNIX
  • \\s0 → \s0

拼接在一起输出:\s-2UNIX\s0

这不是 sed 语法!是 troff 排版语言的命令。

  • \s-2:把后面的字体缩小 2 号
  • \s0:恢复原来字号

输出:on the \s‑2UNIX\s0 Operating System.

2. 捕获分组 \( \) 和回调 \1 \2

正则中用\(内容\)捕获片段;替换域用\1、\2取出对应片段。 示例:交叉引用,把See Section 1.4变成粗体

1
s/\(See Section \)\([1-9][0-9]*\.[1-9][0-9]*\)/\1\\fB\2\\fP/
  • \1:See Section
  • \2:版本号1.4

交换冒号前后两段文本:

1
s/\(.*\):\(.*\)/\2:\1/

输入first:second →输出second:first

3. 替换域中转义&符号

如果想输出字面&,必须写\&

1
s/ORA/O'Reilly \& Associates, Inc./g

校正索引条目

业务场景:troff索引宏.XX "xxx",只修改.XX开头的索引行,不要改动正文。 示例:只修改索引行内单词,地址限制行范围

1
/^\.XX /s/sed, substitution command/sed, substitute command/

进阶shell脚本index.edit:批量提取索引条目,自动生成sed替换脚本。 核心思路:grep提取所有.XX行,交给sed动态拼接生成sed替换命令,输出保存成脚本文件,再批量修改源文件。 坑:如果索引条目包含* .等正则元字符,脚本不会自动转义,会出错,高级处理放在第六章。

删除 d 命令

语法:[address]d

  1. d删除整个模式空间的一行,不是只删除部分字符;只删部分字符要用替换、替换为空字符串。
  2. 执行d之后:清空模式空间,直接读取下一行输入,本脚本后续命令不再对本行执行(改变控制流)。

示例:

1
2
3
/^$/d          # 删除全部空行
/^\.sp/d       # 删除 .sp 开头整行,删除troff空格请求
/^\.TS/,/^\.TE/d # 删除从.TS到.TE整块行

注意区分:大写D是高级多行删除命令,第六章讲解。

追加a、插入i、更改c

三者语法特殊,必须多行书写,不能用分号压缩到一行。

命令 语法 行为 地址限制
i 插入 [line‑address]i\ 文本内容 输出到当前行的前面 只能单个行地址,不能行范围
a 追加 [line‑address]a\ 文本内容 输出到当前行的后面 只能单个行地址,不能行范围
c 更改 [address]c\ 文本内容 清除模式空间,用新文本替换原行内容 支持行范围

语法硬性规则:

  • 命令后面写反斜杠\,然后换行;真正文本写在下一行;
  • 多行文本:除最后一行外,每一行末尾都加\;
  • a/i输出的新增文本不会进入模式空间,后续sed命令不会处理新增出来的文本。

i 插入示例

匹配.Ls行,行前面插入两行troff指令

1
2
3
/^\.Ls/i\
.in 5n\
.sp .3

a追加示例

匹配.Le行,行后面追加两行

1
2
3
/^\.Le/a\
.in 0\
.sp .3

c更改示例

行范围:把从^From到第一个空行之间全部多行,替换成一行<Mail Header Removed>。

1
2
/^From /,/^$/c\
<Mail Header Removed>

坑:如果c写在大括号内部,对范围中每一行都会输出替换文本,会重复输出多次。

c命令执行后清空模式空间,本行后续脚本命令不会执行。

列表 l 命令

语法:[address]l

l,就是sed 的 list 命令(字母小写 L)

命令 l(list):打印模式空间内容;看不见的控制字符会转成八进制转义输出,用来排查看不见的隐藏字符(\r回车、^A、^M退格等)。 一般搭配‑n使用,否则一行输出两遍。

示例:

1
sed -n -e "l" test/spchar

拆解整条命令:

  1. sed:程序
  2. -n:命令行选项,关闭默认输出
  3. -e "l":‑e 代表:后面引号里面内容是 sed 脚本片段
    • "l" 引号内部:l,就是sed 的 list 命令(字母小写 L)
  4. test/spchar:输入文件名

作用:查看文本里面隐藏的^A ^M ^G等看不见的控制字符。

小技巧vi中输入特殊控制字符:Ctrl‑V再按控制键。

案例:清除nroff输出的退格格式

nroff为实现粗体,生成类似N^HN^H序列。

1
2
# 删除 任意字符+退格符 ^H
s/.\^H//g

可以清除man手册输出里粗体、下划线特殊格式。

转换 y 命令

语法:[address]y/源字符集/目标字符集/

按字符位置一一对应转换,不是正则匹配! 源字符第1个→目标第1个;源第2个→目标第2个。没有元字符概念。

示例全部小写转大写:

1
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

注意:y不能只转换单词,会处理整行全部字符;只转换部分内容,需要第六章保持空间hold space。

打印 p 命令

语法:[address]p

输出模式空间内容;不修改模式空间,不改变脚本流程。 不加‑n的情况下,sed默认还要输出一遍,会造成重复打印。

区分:

  • s///p:替换成功才打印(替换标志)
  • p独立命令:无条件打印模式空间。

调试示例:匹配.Ah行,打印原始行,替换后再打印。

1
2
3
4
5
6
#n
/^\.Ah/{
p
s/"//g
s/^\.Ah //p
}

打印行号 =

语法:[line‑address]=

只接受单个行地址,不能写行范围 1,3=,语法错误
= 只会输出行号,不会输出那一行本身的文本
通常配合 -n(或者脚本开头 #n)抑制默认输出;想要同时看到行号 + 行内容,就再加 p 打印命令。

1
2
3
4
5
#n 打印具有if 语句的行号和行
/       if/{
=
p
}

测试文件:

1
2
3
4
5
6
7
8
int main(){
    if(1>0){
        return 1;
    }
    if(2>1){
        return 0;
    }
}

输出:

1
2
3
4
2
    if(1>0){
4
    if(2>1){

只打印匹配 if 的那些行:先输出行号,再输出源代码文本。

next n 命令

语法:[address]n

next命令流程:

  1. 输出当前模式空间内容;
  2. 读取下一行输入,覆盖替换模式空间;
  3. 脚本不从脚本开头重新跑,继续执行n后面的命令。

坑:n前面的命令作用旧行;n后面命令作用新读入的行。

示例:.H1标题行之后,如果紧跟着空行就删除这个空行

1
2
3
4
/^\.H1/{
n
/^$/d
}

逻辑:匹配.H1,输出当前行,读入下一行;如果新行是空行,执行d删除。

普通是「脚本跑完才换内容」;n是「脚本执行半路,强行换掉模式空间里面的内容」。
大写N是高级多行next,第六章。

测试文件:

1
2
3
AAA
BBB
CCC

脚本

1
2
3
4
5
/^AAA/{
    s/AAA/AAA改/
    n
    s/BBB/BBB改/
}

一步步拆解执行

  1. sed 读取 AAA,放入模式空间

模式空间:AAA

  1. 匹配 /^AAA/,进入大括号
  2. s/AAA/AAA改/

模式空间现在变成:AAA改

现在执行n命令,做两件事:
① 输出模式空间当前内容 → 打印AAA改
② 立刻读取文件的下一行BBB,把BBB写入模式空间

重点:模式空间原来存的AAA改直接消失!被新读进来的BBB顶替掉,这就叫覆盖模式空间。

此时模式空间:BBB,旧的AAA改已经不在缓冲区里面了,已经输出完事。

不会跳回脚本开头!继续运行 n 后面的命令:s/BBB/BBB改/

修改现在模式空间里面的BBB → BBB改

大括号全部执行完毕,脚本这一轮结束,输出模式空间内容 →打印BBB改
进入主循环,读取下一行CCC放入模式空间,跑脚本,输出CCC

读 r、写 w 文件

r读文件:[line‑address]r filename

只接受单行地址;匹配行输出完成之后,把外部文件全部内容追加输出;读进来的文本不会进入模式空间,后续sed命令不能修改读入的内容。 文件不存在不会报错。

示例:文件最后一行后面插入closing文件全部内容

1
$r closing

w写文件:[address]w filename

将当前模式空间的内容写入指定文件;匹配就写,不等脚本结束。 文件不存在则创建;多次写同一个文件,是追加内容。sed脚本最多同时打开10个文件。

示例:把按地区把人员分别写入4个文件

1
2
3
4
/Northeast$/w region.northeast
/South$/w region.south
/Midwest$/w region.midwest
/West$/w region.west

退出 q 命令

语法:[line‑address]q

只接受单行地址。一旦执行q:立刻终止整个sed程序,不再读取任何新输入行。

示例:输出文件前100行,读到100行直接退出,类似head。

1
sed '100q' test.txt

性能优势:处理超大文件,找到目标就q退出,不用扫描全部剩余内容。

检查参考页

实战完整综合案例:把纯文本命令参考页,自动加上troff格式化宏。

设计思路:逐行分析输入文本,设计地址与命令;优先处理特殊情况;最后清理通用空行、制表符。 关键点:c更改命令替换大块标签;大括号分组;范围地址;处理各种边界不一致输入。 完整脚本参考书中refsed,运行方式:sed -f refsed refpage输出到屏幕/重定向新文件,不修改原始文件。

本章核心速记卡片(复习)

  1. 语法:[地址]命令;{}分组,}必须独占一行;a/i/c必须多行书写。
  2. 替换s:可以自定义分隔符;替换域元字符只有& \1‑\9 \。
  3. d删除整行;执行d直接读下一行,后续命令不再执行本行。
  4. i前插、a后追加:只能单行地址;c更改支持范围;a/i输出文本不进模式空间。
  5. l查看看不见的控制字符;y字符一一映射转换。
  6. p打印模式空间;=打印行号;n读取下一行,继续执行后续命令。
  7. r读取外部文件追加输出;w把模式空间写入文件。
  8. q找到行直接退出程序,适合大文件提升速度。
  9. 注意区分:标志p/w(替换成功才执行)和独立命令p/w(无条件执行)。