sed 管理模式缓冲区
经过前面几章节的学习,我们对 sed 模式和模式缓冲区应该有了一个大概的了解了。
但是前面对模式和模式缓冲区的使用有点简单了。虽然简单,但已经能够满足日常需要了。
我们并没有深入去了解模式缓冲区到底怎么使用。
就像我们从来没去深入了解 x;n;
中的 x
命令和 n
命令是怎么运作的一样。
本章节,我们就来介绍下模式缓冲区和如何管理模式缓冲区。
首先,我们要介绍的是 n
命令。
数据准备
开始之前,我们先准备下本章节需要的数据文件。
在当前目录下新建一个文件 data.txt
内容如下
1) 小明,23岁,北京大学 2) 小红,22岁,清华大学 3) 小李,25岁,斯坦福大学 4) 小王,22岁,清华大学 5) 小刚,27岁,北京大学 6) 小英,21岁,哈佛大学
在当前目录下新建另一个文件 demo.txt
内容如下
1) 小明 2) 23岁,北京大学 3) 小红 4) 22岁,清华大学 5) 小李 6) 25岁,斯坦福大学 7) 小王 8) 22岁,清华大学 9) 小刚 10) 27岁,北京大学 11) 小英 12) 21岁,哈佛大学
n 命令
单字母 n
命令用于 刷新模式缓冲区。
精确的来讲,n
命令会执行以下几步:
- 将模式缓冲区中的数据输出到标准输出
- 然后清空模式缓冲区
- 从输入源中读取下一行存放到模式缓冲区中
- 对模式缓冲区的数据应用所有的 sed 命令
n
命令的语法格式如下
[address1[,address2]]n
可选参数 address1
和 address2
用于指定要读取的行。
范例1: n 命令的简单使用
我们简单的使用 n
命令来输出文件的内容
[www.twle.cn]$ sed 'n' data.txt
运行结果如下
1) 小明,23岁,北京大学 2) 小红,22岁,清华大学 3) 小李,25岁,斯坦福大学 4) 小王,22岁,清华大学 5) 小刚,27岁,北京大学 6) 小英,21岁,哈佛大学
如果你有足够的好奇心,在命令里加上 -n
选项
哈哈,输出结果如下
[www.twle.cn] sed -n 'n' data.txt
输出为空,是的,空。
为什么呢?
留给大家自己想吧。
n 命令混合使用
n
命令通常和其它命令一起使用。
假设我们存在一个复杂的 sed 命令 ,内容如下
Sed command #1 Sed command #2 Sed command #3 n command Sed command #4 Sed command #5
在这个命令中,n
命令之前有三个 sed 命令,n
命令之后有两个 sed 命令。
当把这些命令运用到 data.txt
的时候,具体的工作流程和输出结果又是什么呢?
利用所学知识,我们先尝试解答下:
- 先从源文件中读取一行,保存到 模式缓冲区 中
- 接着把前三条命令运用到模式缓冲区中的数据。
- 然后把模式缓冲区中的数据输出到标准输出,同时再从源文件中读取一样放到模式缓冲区中。
- 最后把第四第五条命令运用到模式缓冲区中的数据。
- 循环 1-4 直到文件末尾。
上面这个流程你必须完全理解,不然接下来的内容会有难度的。重点来讲,只要理解了 n
命令的作用就差不多了。
范例
为了加深大家对上面流程的理解,我们使用一个小范例来演示一下
[www.twle.cn]$ sed -n 'p;n;' data.txt
很简单,我们没有设置那么多的命令,就是 n
命令之前有一个输出命令,n
命令之后什么都没有。
我们使用 -n
选项禁止默认输出,也就是说,除了 p
命令会输出外,没有其它输出
上面的命令运行结果如下
1) 小明,23岁,北京大学 3) 小李,25岁,斯坦福大学 5) 小刚,27岁,北京大学
从结果中可以看到,因为 n
命令后无输出,因此 n
命令读取的内容被放弃了。
如果你觉得这样不好理解,那么可以删除 -n
选项。删除后运行结果如下
[www.twle.cn]$ sed 'p;n;' data.txt 1) 小明,23岁,北京大学 1) 小明,23岁,北京大学 2) 小红,22岁,清华大学 3) 小李,25岁,斯坦福大学 3) 小李,25岁,斯坦福大学 4) 小王,22岁,清华大学 5) 小刚,27岁,北京大学 5) 小刚,27岁,北京大学 6) 小英,21岁,哈佛大学
默认情况下,每次清空模式缓冲区的时候,都会把模式缓冲区的内容输出到标准输出。
所以我们看到了正常的 6 条数据都输出了。
从某些方面讲,之前的表述有点不严谨了,不过这都是方便大家学习,一开始就将的如此深奥,恐怕你早早的就放弃了。
数据缓存区 hold buffer
在 sed 工作流程 里我们有提到 数据缓存区( hold buffer
)。
原文大概是这样的
sed 还在内存上开辟了另一个私有的缓冲区 hold buffer 用于保存处理后的数据以供以后检索。
每一个周期执行结束,sed 会清空 pattern buffer 缓冲区的内容,但 hold buffer 缓冲区的内容并不会清空。
hold buffer 缓冲区用于存储处理后数据,sed 命令并不会对这里的数据处理。
这样,当 sed 需要之前处理后的数据时,可以随时从 hold buffer 缓冲区读取。
数据缓存区用于存储之前处理好的数据,sed 命令并不会对这里的数据处理。
但有时候我们需要从数据缓存区读取数据然后保存到模式缓冲区里。
必须这么做的原因是无法从其它任何地方来获取之前处理好的数据。
路就只有这么一条,不走也得走啊。
因此,我们就需要那么一个命令,这个命令可以从数据缓存区读取行到模式缓冲区里。
sed 提供了这样的命令,它就是单字母 x
命令。
x 命令
单字母 x
命令用于从 数据缓存区 读取数据到 模式缓冲区 里。
单字母 x
命令是 exchange
的缩写。
至于为什么是 x
而不是 e
?
因为 e
已经被使用了,所以只能选 x
了。
x
命令的语法格式如下
[address1[,address2]]x
可选参数 address1
和 address2
用于指定要读取的行。
范例: x 命令的复杂使用
在范例之前,我们先来看看我们的数据文件 data.txt
的内容。
1) 小明,23岁,北京大学 2) 小红,22岁,清华大学 3) 小李,25岁,斯坦福大学 4) 小王,22岁,清华大学 5) 小刚,27岁,北京大学 6) 小英,21岁,哈佛大学
放心,没改,只是显示出来方便对比而已。
我们来写一个小小的 sed 程序。这个程序的流程如下
- 从源文件中读取一行放到缓冲区(默认)
- 在模式缓冲区里的数据开头添加
NO.
- 输出模式缓冲区的数据
- 从源文件中读取下一行到模式缓冲区里
- 输出模式缓冲区的数据
- 从数据暂存区里读取最近的一行数据保存到模式缓冲区里
- 输出模式缓冲区的数据
完整的 sed 命令如下
[www.twle.cn]$ sed -n 's/^/NO.&/;p;n;p;x;p' data.txt
你能想象出数据的执行结果吗?
你能想象出数据的流动逻辑吗?
我们还是先看运行结果把。上面的命令运行结果如下
NO.1) 小明,23岁,北京大学 2) 小红,22岁,清华大学 NO.3) 小李,25岁,斯坦福大学 4) 小王,22岁,清华大学 2) 小红,22岁,清华大学 NO.5) 小刚,27岁,北京大学 6) 小英,21岁,哈佛大学 4) 小王,22岁,清华大学
我们来解释下上面这个看似简单的 's/^/NO.&/;p;n;p;x;p'
命令的执行流程
-
首先,sed 读取第一行数据,也就是
1) 小明,23岁,北京大学
,保存到模式缓冲区里 -
然后对模式缓冲区的数据应用第一条命令
s/^/NO.&/
,它会在行的开头添加NO.
-
然后对模式缓冲区的数据应用第二条命令
p
,p
命令直接输出模式缓冲区的内容。于是控制台出现下面的内容
NO.1) 小明,23岁,北京大学
-
然后对模式缓冲区的数据应用第三条命令
n
,因为n
会刷新缓冲区。n
命令并不会把模式缓冲区里的数据暂存到数据缓存出去。而是直接先清空模式缓冲区,然后从源文件读取下一行数据保存到模式缓冲区里。
因此这时候的模式缓冲区的数据为
2) 小红,22岁,清华大学
-
然后对模式缓冲区的数据应用第四条命令
p
,p
命令直接输出模式缓冲区的内容。于是控制台出现下面的内容
2) 小红,22岁,清华大学
-
然后对模式缓冲区的数据应用第五条命令
x
,x
命令会从数据缓存区里读取最新的一行数据并保存到模式缓冲区里。因为初始化的时候数据暂存区是空的,而之前的命令并不会将模式缓冲区里的数据保存到数据暂存区
因此这个时候,数据暂存区还是空的。
那么
x
命令运行完之后,模式缓冲区里也是空的。 -
然后对模式缓冲区的数据应用第六条命令
p
,p
命令直接输出模式缓冲区的内容。因为模式缓冲区里是空的,所以输出了一个空行
-
第七个命令运行完之后,所有的命令都运用到了模式缓冲区里。这时候 sed 就会把模式缓冲区里的数据保存到数据缓冲区。
也就是说此时,数据缓冲区里的内容为
2) 小红,22岁,清华大学
-
重复以上 1-8 直到文件结束。
经过上面的一番解释,不知道你明白了吗?
千万不要头晕......
范例 2: x 命令的复杂使用
有时候回过头来想想,并不是 x 命令有多复杂,而是 sed 的很多流程不清不白的。
经过上一个范例的责难,我们开始下一个范例吧,这个范例,想必,应该很简单吧?
你能猜出最后运行的结果吗?
[www.twle.cn]$ sed -n 'x;n;x;p' data.txt
运行结果如下
1) 小明,23岁,北京大学 3) 小李,25岁,斯坦福大学 5) 小刚,27岁,北京大学
猜中了吗?
h 命令
h
命令用于操作 数据缓存区。h
命令用于从 模式缓冲区 里 复制 数据到 数据缓存区。
h
命令在复制数据前会先清空 数据缓存区。
注意,h
命令不是移动数据,而是制作数据的一个副本。模式缓冲区 里的数据并不会删除。
h 命令语法
h
命令的语法格式如下
[address1[,address2]]h
可选参数 address1
和 address2
用于指定要读取的行。
如果都没有指定,则读取最近刚添加的那行。
范例
下面的 sed 命令我们只输出学校为北京大学的学生姓名。
[www.twle.cn]$ sed -n '/北京大学/!h; /北京大学/{x;p}' demo.txt
命令运行结果如下
1) 小明 9) 小刚
你能理解这个范例的运行流程吗?
我们这里简单的解释一下吧。
-
我们先来看看学生数据的格式。
[www.twle.cn]$ cat demo.txt 1) 小明 2) 23岁,北京大学 3) 小红 4) 22岁,清华大学 5) 小李 6) 25岁,斯坦福大学 7) 小王 8) 22岁,清华大学 9) 小刚 10) 27岁,北京大学 11) 小英 12) 21岁,哈佛大学
这份数据,每两行为一个学生,奇数行表示学生姓名,偶数行是对应的年龄和学校。
-
感叹号
!
的作用。感叹号
!
用于反转测试条件,也就是 模式缓冲区 里的数据不包含 北京大学 时,才会将数据从模式缓冲区复制到数据缓存区。 -
大括号
{}
用于组织多个 sed 命令,其它语言的大括号作用是一样的。
完整的流程如下
-
刚开始 sed 从输入源里读取第一行数据放到 模式缓冲区。
因此模式缓冲区里的数据为
1) 小明
-
然后对模式缓冲区里的数据应用第一条命令
/北京大学/!h
。检查是否包含 北京大学,如果答案是不包含,则从 模式缓冲区 里 复制 数据到 数据缓存区。
这条命令执行后,数据缓存区 里的数据为
1) 小明
-
然后对模式缓冲区里的数据应用第二条命令
/北京大学/{x;p}
。检查是否包含 北京大学,如果答案是包含,则从 数据缓存区 里 读取 数据到 模式缓冲区。
因为此时 模式缓冲区 里的数据是
1) 小明
所以不包含。第一个执行周期到此结束。
-
开始进入第二个执行周期,sed 从输入源里读取第二行数据放到 模式缓冲区。
因此模式缓冲区里的数据为
2) 23岁,北京大学
-
然后对模式缓冲区里的数据应用第一条命令
/北京大学/!h
。检查是否包含 北京大学。
因为包含,所以什么事情都不做
-
然后对模式缓冲区里的数据应用第二条命令
/北京大学/{x;p}
。检查是否包含 北京大学,如果答案是包含,则从 数据缓存区 里 读取 数据到 模式缓冲区。
因为此时 模式缓冲区 里的数据是
2) 23岁,北京大学
所以包含。因为包含,所以条件测试为真。从 数据缓存区 里 读取 最新一行数据到 模式缓冲区。
因为此时 数据缓存区 里最新的一行数据是 1) 小明。
因此
x
命令执行后,模式缓冲区 的数据是 1) 小明。然后使用
p
命令输出 模式缓冲区,因此控制台出现1) 小明
。第二个执行周期到此结束。
-
不断重复 1-3 直到文件末尾。
总的来说,流程相对还是简单的。
H 命令
小写字母 h
命令很好用,但它有一个缺点,它会先清空 数据缓存区 里的内容。
这有时候是不能接受的。因为有时候我们需要保留完整的内容。
为了能够有 h
命令相同的功能,但并不会清空数据而是在原有数据后面追加这种需求。
大写字母 H
命令诞生了。
H
命令同样用于操作 数据缓存区。H
命令同样可以用于从 模式缓冲区 里 复制 数据到 数据缓存区。
但是,H
命令并不会在复制时清空原有数据,而是在原有数据后面 追加一个新行。原有的数据保持不变。
注意,H
命令不是移动数据,而是制作数据的一个副本。模式缓冲区 里的数据并不会删除。
h 命令语法
H
命令的语法格式如下
[address1[,address2]]H
可选参数 address1
和 address2
用于指定要读取的行。
如果都没有指定,则读取最近刚添加的那行。
范例
这个范例,我们打算来点新鲜的,我们输出学校为 北京大学 的那些学生姓名,然后在新行里输出对应的年龄和学校。
看起来是不是有难度,哈哈,但是,当你看到命令就不会觉得难了。
[www.twle.cn]$ sed -n '/北京大学/!h; /北京大学/{H;x;p}' demo.txt
运行结果如下
1) 小明 2) 23岁,北京大学 9) 小刚 10) 27岁,北京大学
流程和上面 h
命令的流程一样,不一样的地方在于 第三点。
因为第二条命令里多了 H
这个命令。
当匹配成功后,H
命令从 模式缓冲区 里 复制 数据到 数据缓存区。因此此时 数据缓存区 里的数据为
1) 小明 2) 23岁,北京大学
后面的流程就一样了。
g 命令
我们已经学习了如何将 模式缓冲区 里的数据 复制/追加 到 数据缓存区。
那么反向的功能有吗?有命令能够实现把 数据缓存区 里的数据 复制/追加 到 模式缓冲区 吗?
答案是肯定的。
sed 提供了 g
命令用于将 数据缓存区 里的数据 复制 到 模式缓冲区。
因为是复制,所以 模式缓冲区 里的原有数据会被清空。
g 命令语法格式
g
命令的语法格式如下
[address1[,address2]]g
可选参数 address1
和 address2
用于指定要读取的行。
如果都没有指定,则读取最近刚添加的那行。
苹果电脑自带的 sed 不支持 G 命令。
范例
我们使用 g 命令来实现上面范例相同的功能,输出人名和对应的年龄学校。
不过我们这次做一点小小的改动,我们先输出人名,然后在下一行输出年龄和学校。
下面的 sed 命令,输出学校为北京大学的所有人名以及年龄学校。
[www.twle.cn]$ sed -n '/北京大学/!h; /北京大学/{p;g;p}' demo.txt
运行结果如下
2) 23岁,北京大学 1) 小明 10) 27岁,北京大学 9) 小刚
这条命令咋一看和上面的 H
命令的范例有点类似。
因此整个流程我们就不介绍了,直接介绍第二条命令。
第二条命令是 /北京大学/{p;g;p}
。
这条命令的执行流程如下
-
检查 模式缓冲区 里的数据是否包含 北京大学。
-
如果包含,则
p
命令先输出 模式缓冲区 里的数据。因此,控制台第一个出现的是
2) 23岁,北京大学
-
p
命令之后是g
命令。g
命令用于将 数据缓存区 里的最新一行数据 复制 到 模式缓冲区。因此,
g
命令执行完之后, 模式缓冲区 里的数据是1) 小明
-
g
命令之后是p
命令用于输出 模式缓冲区 里的数据。因此,控制台出现的第二行内容是
1) 小明
哈哈,是不是简单明了。
G 命令
单字母大写 G
命令用于将 数据缓存区 里的数据追加到 模式缓冲区 里。
这个命令不是使用 数据缓存区 的里数据替换 模式缓冲区 里的数据,而是放到 模式缓冲区 原有的数据后面。
当然了,这个追加也不是直接放到同一行里,而是创建一个新行。
也就是说,当 G 命令运行完之后,模式缓冲区里会有两行的数据。
苹果电脑自带的 sed 不支持 G 命令。
G 命令的语法格式如下
[address1[,address2]]G
可选参数 address1
和 address2
用于指定要读取的行。
范例
我们复用下上面的代码,将小写的 g
改成大写的 G
即可。
[www.twle.cn]$ sed -n '/北京大学/!h; /北京大学/{G;p}' demo.txt
运行结果如下
2) 23岁,北京大学 1) 小明 10) 27岁,北京大学 9) 小刚
范例2
上面的范例,如果我们想调换下输出顺序,名字在前,然后是他的年龄和学校,可以在 G
命令之前添加 x
命令
[jerry]$ sed -n '/Paulo/!h; /Paulo/{x;G;p}' books.txt
运行上面的代码,输出结果如下
1) 小明 2) 23岁,北京大学 9) 小刚 10) 27岁,北京大学