sed一般用来对单行字符串进行处理;当需要使用sed命令来处理多行字符串时, 就涉及到sed的一些比较高级的用法。
目录
模式空间和保持空间
模式空间(pattern space): 模式空间用于 sed 执行的正常流程中。该空间 sed 内置的一个缓冲区,用来存放、修改从输入文件读取的内容,每处理完一行都会清空模式空间在读取下一行。
保持空间(hold space):保持空间是另外一个缓冲区,用来存放临时数据。sed 可以在保持空间和模式空间和模式空间交换数据,但是不能在保持空间上执行普通的 sed 命令。每次循环读取数据过程中,模式空间的内容都会被清空,然而保持空间的内容则保持不变,不会在循环中被删除。(保持空间默认有换行符,文件里有几行内容就有几行换行符)
在使用一般命令时(即那些不涉及到多行处理的命令)只会用到模式空间,sed将处理的行读入模式空间,脚本中的“sed command(sed命令)”就一条接着一条进行处理,直到脚本执行完毕。然后该行被输出,模式被清空(保持空间中的内容仍然存在);接着,在重复执行刚才的动作,文件中的新的一行被读入,直到文件处理完毕。

sedsed 帮助查看sed命令执行过程
sedsed简介
sedsed是用python(python2)写的一个脚本文件
sedsed命令能帮助查看在sed命令执行过程中, 模式空间和保存空间中存储的内容;
安装
- 先用echo $PATH获取设备调用命令相关的目录路径;
- cd到其中一个目录中去;
- 下载sedsed:wget http://aurelio.net/projects/sedsed/sedsed-1.0 -O sedsed
- 因为现在linux中默认安装的python版本大都是python3, 所以要把python2安装一下;yum(apt) install python2; 安装后which一下, 获取python2调用路径;
- 修改sedsed文件第一行, 改为:#!/usr/bin/env(第4步获取的路径);
- 加执行权限: chmod a+x sedsed
使用
和sed一样, 另外添加一个-d
使用调试模式,以便于查看命令执行过程中模式空间和保存空间的内容;
root@centos8: sed$ seq 4 | sedsed -nd 'N;l;D' PATT:1$ HOLD:$ COMM:N PATT:1\n2$ HOLD:$ COMM:l 1\n2$ PATT:1\n2$ HOLD:$ COMM:D PATT:2$ HOLD:$ COMM:N PATT:2\n3$ HOLD:$ COMM:l 2\n3$ PATT:2\n3$ HOLD:$ COMM:D PATT:3$ HOLD:$ COMM:N PATT:3\n4$ HOLD:$ COMM:l 3\n4$ PATT:3\n4$ HOLD:$ COMM:D PATT:4$ HOLD:$ COMM:N root@centos8: sed$
高级命令
主要指的是一些涉及到模式空间和保持空间的命令. 基本上都是参考1链接中的内容.
- h:把模式空间中的内容覆盖至保持空间中
- H:把模式空间中的内容追加至保持空间中
- g:把保持空间中的内容覆盖至模式空间
- G:把保持空间中的内容追加至模式空间
- n:如果自动打印没有被禁用,打印模式空间,然后,无论如何,用下一输入行内容替换模式空间。如果没有更多的输入, 则不执行更多命令退出sed。
- N:将下一行添加到模式空间中。将当前读入行和用N命令添加的下一行看成“一行”。
- d:删除模式空间中的行
- D:删除多行模式空间中的所有行
- x:将模式空间中的内容和保持空间中的内容互换
- p: 打印模式空间内容到标准输出。
- P: 大写P打印模式空间中内容至第一个换行符(含)为止。

n和N 读入下一行并覆盖/追加
- n:如果自动打印没有被禁用,打印模式空间,然后,无论如何,用下一输入行内容替换模式空间。如果没有更多的输入, 则不执行更多命令退出sed。
- N:将下一行添加到模式空间中。将当前读入行和用N命令添加的下一行看成“一行”。
n
的用法
root@centos8: sed$ seq 4 | sedsed -d 'n;p' PATT:1$ # 先读入第一行:1到模式空间 HOLD:$ COMM:n # 开始执行n命令, 由于没有-n,即自动打印没有被禁用; 则先将此时的模式空间中的内容打印出来. 屏幕打印出1; 然后,再用下一行输入内容替换掉模式空间, 所以此时模式空间中的内容变为2 1 PATT:2$ HOLD:$ COMM:p # 再执行p命令, 即打印此时模式空间中的内容. 屏幕打印出2; 此时结束第一次执行. 2 PATT:2$ HOLD:$ 2 # 由于命令执行完了, 且自动打印没有被禁用, 所以又会打印此时模式空间中的内容, 即屏幕打印2; 并将模式空间中的内容清空; PATT:3$ # 然后开始处理下一行; 由于之前由于n命令, 已经读入了原本的第二行; 所以这里读入的是原本的第三行内容; 即:3; 读入后, 模式空间内容变为3; HOLD:$ COMM:n # 遇上n命令, 输出模式空间内容, 将3打印到屏幕上; 清空模式空间,并读入下一行内容,即:模式空间内容变为4; 3 PATT:4$ HOLD:$ COMM:p # 遇到p, 打印模式空间内容, 屏幕打印4 4 PATT:4$ HOLD:$ 4 # 第二次流程执行完了. 输出此时模式空间内容:4, 并将模式空间清空; # 用sed执行结果一致; root@centos8: sed$ seq 4 | sed 'n;p' 1 2 2 3 4 4 ########################下面是不带禁用自动打印时的输出. 更直观一些######################## root@centos8: sed$ seq 4 | sed -n 'n;p' 2 4 root@centos8: sed$ seq 4 | sedsed -nd 'n;p' PATT:1$ HOLD:$ COMM:n # 因为自动打印被禁用了, 所以此时执行n命令不会先打印模式空间内容; 直接用下一行替换掉; PATT:2$ HOLD:$ COMM:p 2 PATT:2$ HOLD:$ PATT:3$ HOLD:$ COMM:n PATT:4$ HOLD:$ COMM:p 4 PATT:4$ HOLD:$ root@centos8: sed$
从上面的例子可以看出n的一个用法: 使命令隔行执行;
比如说:
root@centos8: sed$ seq 6 | sed -nE 'n;s/([[:digit:]])/\1 is substituded/;p' 2 is substituded 4 is substituded 6 is substituded root@centos8: sed$ seq 6 | sed -E 'n;s/([[:digit:]])/\1 is substituded/' 1 2 is substituded 3 4 is substituded 5 6 is substituded root@centos8: sed$
多个n连在一块还可以实现指定步长. 和指定步长的地址指定方法结果一样.
root@centos8: sed$ seq 9 | sed -n 'n;n;p' 3 6 9 root@centos8: sed$ seq 9 | sed -n '0~3p' 3 6 9 root@centos8: sed$
N
的用法
root@centos8: sed$ seq 6 | sed -n 'N;l' 1\n2$ 3\n4$ 5\n6$ root@centos8: sed$ seq 6 | sed 'N;l' 1\n2$ 1 2 3\n4$ 3 4 5\n6$ 5 6 root@centos8: sed$ ########################下面用sedsed看执行过程中模式空间中的内容######################## root@centos8: sed$ seq 6 | sedsed -nd 'N;l' PATT:1$ HOLD:$ COMM:N # N命令会将模式空间后面添加换行符\n, 并读入下一行内容添加到后面; [和n相比, 是追加而不是覆盖; 也不会将模式空间原有内容打印出来] PATT:1\n2$ HOLD:$ COMM:l # 用l命令将特殊字符"可视化"打印出来 1\n2$ PATT:1\n2$ HOLD:$ PATT:3$ # 由于禁用自动打印; 命令执行完后, 不会打印模式空间内容; 直接清空并读入下一行; HOLD:$ COMM:N PATT:3\n4$ HOLD:$ COMM:l 3\n4$ PATT:3\n4$ HOLD:$ PATT:5$ HOLD:$ COMM:N PATT:5\n6$ HOLD:$ COMM:l 5\n6$ PATT:5\n6$ HOLD:$ root@centos8: sed$root@centos8: sed$ seq 6 | sedsed -d 'N;l' PATT:1$ HOLD:$ COMM:N PATT:1\n2$ HOLD:$ COMM:l 1\n2$ PATT:1\n2$ HOLD:$ 1 # 和自动打印被禁用了相比, 会在命令执行完一轮后, 将模式空间中的内容打印出来; 2 PATT:3$ HOLD:$ COMM:N PATT:3\n4$ HOLD:$ COMM:l 3\n4$ PATT:3\n4$ HOLD:$ 3 4 PATT:5$ HOLD:$ COMM:N PATT:5\n6$ HOLD:$ COMM:l 5\n6$ PATT:5\n6$ HOLD:$ 5 6 root@centos8: sed$
h/H 将模式空间内容覆盖/追加到保持空间
用sedsed看h对模式空间和保持空间的影响
root@centos8: sed$ seq 3 | sedsed -d 'h' PATT:1$ HOLD:$ COMM:h # 将模式空间中的内容覆盖到保持空间中去; 同时, 模式空间中的内容不会变; PATT:1$ HOLD:1$ 1 PATT:2$ HOLD:1$ COMM:h PATT:2$ HOLD:2$ 2 PATT:3$ HOLD:2$ COMM:h PATT:3$ HOLD:3$ 3 root@centos8: sed$ seq 3 | sedsed -nd 'N;h' PATT:1$ HOLD:$ COMM:N PATT:1\n2$ HOLD:$ COMM:h PATT:1\n2$ HOLD:1\n2$ PATT:3$ # 读新行; 并将模式空间清空; HOLD:1\n2$ COMM:N root@centos8: sed$
用sedsed看H对保持空间的影响
root@centos8: sed$ seq 6 | sedsed -nd 'N;H' PATT:1$ HOLD:$ COMM:N PATT:1\n2$ HOLD:$ COMM:H PATT:1\n2$ HOLD:\n1\n2$ # 这里在将模式空间追加到保持空间时, 因为会先加换行,在追加内容; 所以这里最前面有个空行; PATT:3$ HOLD:\n1\n2$ COMM:N PATT:3\n4$ HOLD:\n1\n2$ COMM:H PATT:3\n4$ HOLD:\n1\n2\n3\n4$ PATT:5$ HOLD:\n1\n2\n3\n4$ COMM:N PATT:5\n6$ HOLD:\n1\n2\n3\n4$ COMM:H PATT:5\n6$ HOLD:\n1\n2\n3\n4\n5\n6$ root@centos8: sed$
g/G 将保持空间内容覆盖/追加到模式空间
g/G和h/H类似, 数据流方向相反; g/G是将保持空间中的内容覆盖/追加到模式空间中去;
可以像上面那样用sedsed看它们执行之间状态;
x 交换模式空间和保持空间中的内容
root@centos8: sed$ seq 3 | sedsed -dn 'H;x;p' PATT:1$ HOLD:$ COMM:H PATT:1$ HOLD:\n1$ COMM:x PATT:\n1$ HOLD:1$ COMM:p 1 PATT:\n1$ HOLD:1$ PATT:2$ HOLD:1$ COMM:H PATT:2$ HOLD:1\n2$ COMM:x PATT:1\n2$ HOLD:2$ COMM:p 1 2 PATT:1\n2$ HOLD:2$ PATT:3$ HOLD:2$ COMM:H PATT:3$ HOLD:2\n3$ COMM:x PATT:2\n3$ HOLD:3$ COMM:p 2 3 PATT:2\n3$ HOLD:3$ root@centos8: sed$
分支和流程控制
这块的内容基本和参考4中的内容一致
分支命令b、t和T能够改变sed程序的执行流程。(译者:可以把无条件分支b命令理解成C语言或VB语言的goto语句,而条件分支命令t和T可以看成其他编程语言的if/then语句。)
默认情况下,sed在读取一输入行到模式缓冲区后,按顺序执行脚本中的所有命令。没有地址的命令影响所有行。有地址的命令只会影响匹配的行。参见6.1节[循环执行]和4.1节[地址摘要]。
sed不支持传统的if/then结构。取而代之,一些命令可以用作条件或更改默认流程控制:
d 删除(delete)或清理当前模式空间,并在没有执行剩余命令和不打印模式空间的情况下,开启下一轮循环。 D 删除模式空间中第一个换行符及其前面的内容,并在没有执行剩余命令和不打印模式空间的情况下,开启下一轮循环。 [addr]X [addr]{X; X; X} /regexp/X /regexp/{X; X; X} 地址和正则表达式可以用作if/then条件。如果[addr]匹配当前模式空间,执行这个或这些命令(译者:X是命令的占位符)。 例如,命令‘/^#/d’的意思是:如果当前模式匹配正则表达式‘^#’——即以#开头的行,那么执行d命令——删除该行,也没有打印 它, 然后立即重启下一轮循环。 b 无条件分支——总是跳转到一个标签处(译者:如果省略标签,可以理解成跳转到脚本的结尾,开始下一轮循环),逃过或者重复 执行其他的命令,也不会重启新一轮循环。如果结合一个地址,分支可以在匹配的行上有条件地执行。 t 条件分支——即只有在上一输入行上成功执行‘s///’命令,或者另一个条件分支被执行,才会跳转到一个标签处。 T 与t命令相似但条件相反——即只有在上一个输入行上执行失败,才会跳转到一个标签处。
下面两个sed程序是等价的。第一个巧妙设计的示例使用b命令跳过匹配‘1’的行上执行s///命令。第二个示例使用一个地址,后带‘!’(表示取反),在不匹配‘1’的行上执行替换命令,然后,‘y///’命令仍然会在所有行上执行:
$ printf '%s\n' a1 a2 a3 | sed -E '/1/bx; s/a/z/; :x; y/123/456/' a4 z5 z6 $ printf '%s\n' a1 a2 a3 | sed -E '/1/!s/a/z/; y/123/456/' a4 z5 z6
6.4.1 分支和循环
b、t和T命令后面可以跟随一个标签(通常是一个单字符)。标签是通过组合一个冒号及一个或者多个字符来指定(例如:‘:x’)。如果标签省略了,分支命令就重启循环。注意分支跳转到一个标签和重启循环之间的区别:当重启循环时,sed先打印模式空间中的当前内容,然后,读取下一输入行到模式空间(译者:我把它理解为程序执行到达脚本结尾,这时,如果没有禁用自动打印,就会打印模式空间,然后读取下一个输入行);而跳转到一个标签不会打印模式空间,也不会读取下一个输入行,即使标签在程序的开始位置也是如此。
下面的程序没有操作。程序中仅有的b命令没有标签可跳转,这样,仅仅是重启循环而已。每个循环,打印模式空间,读取下一输入行:
$ seq 3 | sed b 1 2 3
下面的示例是一个无限循环——它不会终止,也不会打印任何内容。b命令跳转到“x”标签处,新的循环永远不会开始:
$ seq 3 | sed ‘:x; bx’ # 上面的命令要求GNU sed,它支持一个标签后可跟另外的命令,两者之间用分号(;)分隔,而不必是换行符。可移植性的等价脚本: # sed -e ‘:x’ -e bx
分支命令通常与n或N命令互补:这两个命令都将会读取下一输入行到模式空间中,而不等待重新启动下一轮循环。在读取下一个输入行之前,n命令先打印当前模式空间,然后清空它;而N命令先向模式空间中追加一个换行符,再追加下一输入行。比较下面两个示例:
$ seq 3 | sed ‘:x; n; bx’ 1 2 3 $ sed 3 | sed ‘:x; N; bx’ 1 2 3
- 两个示例都不是无限循环,尽管从来没有开始一个新的循环。
- 第一个示例,n命令先打印模式空间中的内容,然后清空模式空间,再者读取下一个输入行。
- 第二个示例,N命令把一个换行符和下一输入行追加到模式空间中。这些行累积在模式空间中,直到没有更多的输入行可以读取,那么,N命令终止sed程序。当程序终止时,循环结束的动作被执行,整个模式空间打印出来。
- 第二个示例要求GNU sed,因为它使用N命令的非POSIX标准行为。参见第10章“最后一行上执行N命令”段落[报告bugs]。
- 为了进一步测试两个示例的不同,试一试下面的命令:
printf '%s\n' aa bb cc dd | sed ':x ; n ; = ; bx' aa 2 bb 3 cc 4 dd
(译者:sed先读取第一行“aa”,遇到n命令,打印“aa”,且清空模式空间,然后读取下一行“bb”到模式空间中;命令=打印行号,这时行号已经是2,不是1,并换行;遇到b命令跳转到x标签位置,然后继续前面的操作)。
printf '%s\n' aa bb cc dd | sed ':x ; N ; = ; bx' 2 3 4 aa bb cc dd
(译者:sed先读取第一行“aa”,遇到N命令,读取下一行“bb”,并把一个换行符和这一行追加到模式空间中——即:“aa\nbb”;命令=打印行号,同上一个示例行号已经是2,并换行;遇到b命令跳转到x标签位置,然后继续前面的操作。这样每次b跳转前会打印编号,而输入的行全部累积在模式空间中,到N命令没有可读取的时候,sed结束前打印模式空间的包含换行符的多行——“aa\nbb\ncc\ndd”。)
printf '%s\n' aa bb cc dd | sed ':x ; n ; s/\n/***/ ; bx' aa bb cc dd
(sed先读取第一行“aa”,遇到n命令,打印“aa”,且清空模式空间,然后读取下一行“bb”到模式空间中;由于模式空间内为空,无法匹配到换行符,替换命令不会执行;遇到b命令跳转到x标签位置,然后继续前面的操作)
printf '%s\n' aa bb cc dd | sed ':x ; N ; s/\n/***/ ; bx' aa***bb***cc***dd
(译者:sed先读取第一行“aa”,遇到N命令,读取下一行“bb”,并把一个换行符和这一行追加到模式空间中——即:“aa\nbb”;由于匹配“\n”成功,所以用字符串“***”替换匹配到的内容——模式空间内的内容为:“aa***bb”;遇到b命令跳转到x标签位置,然后继续前面的操作。这样每次遇到b命令跳转前都会执行“s///”命令,把“\n”替换掉,而被替换后内容全部累积在模式空间中,到N命令没有可读取的时候,sed结束前打印模式空间的内容——“aa***bb***cc***dd”。)
6.4.2 分支命令示例:跳转行
作为一个真实使用分支命令示例,考虑“带引用可打印”(quoted-printable)文件,通常用于对电子邮件进行编码。在这些文件中,长行被拆分,并在行尾用一个由单个“=”字符组成的软换行符进行标记:(参考网页如下:https://en.wikipedia.org/wiki/Quoted-printable)
(译者:quoted-printable是带引号可打印或QP编码:是指一种使用可打印的ASCII字符(字母数字和等号“=”)在7位数据路径上传输8位数据的编码,或通常在不能正确处理8位字符串编码的介质上传输8位数据的编码。从历史上看,电子邮件通常涉及不能处理8位字符串编码,因为各种介质被用来传输消息,有时在比互联网上使用更多。)
$ cat jaques.txt All the wor= ld’s a stag= e, And all the= men and wo= men merely = players: They have t= heir exits = and their e= ntrances; And one man= in his tim= e plays man= y parts.
下面的程序使用一个地址表达式“/=$/”作为条件,如果当前模式空间的行内容以“=”结尾,就使用N命令读取下一输入行,(译者:并把一个换行符和该行追加到模式空间中,)然后,使用s命令把所有的“=\n”字符序列予以清理,然后,遇到无条件的b命令导致在没有重启新一轮循环前提下,流程跳转到程序的开始处。如果模式空间没有以“=”结尾,不会执行这个命令组,那么默认动作会执行——打印模式空间,且开启新一轮循环:
$ sed ':x; /=$/{N; s/=\n//g; bx}' jaques.txt All the world’s a stage, And all themen and women merely players: They have their exits and their entrances; And one manin his time plays many parts.
下面是一个方法稍微不同的替代程序。除了最后一行以外的所有输入行,N命令都会把一个换行符和下一行追加到模式空间中。通过s命令删除软换行符(即在行尾的“=\n”)。如果替换成功(意味着模式空间中有了一应该被连接的行),那么,条件分支命令t会使流程跳转到程序的开始处,也没有重启循环。如果这个替换失败(意味着没有软换行符),t命令不会跳转。然后,P命令会打印第一个换行符及其前面的内容,且D命令会删除这个换行符及其前面的内容。(想要学习更多有关N、P和D命令参见6.3节[多行技术])。
$ sed ':x; $!N; s/=\n//; tx; P; D' jaques.txt
结果与前面一样。
参考
sed高阶用法 保持空间和模式空间命令(n N x h H g G P t) (里面内容有些是错的)
留言