sed一般用来对单行字符串进行处理;当需要使用sed命令来处理多行字符串时, 就涉及到sed的一些比较高级的用法。

模式空间和保持空间

模式空间(pattern space): 模式空间用于 sed 执行的正常流程中。该空间 sed 内置的一个缓冲区,用来存放、修改从输入文件读取的内容,每处理完一行都会清空模式空间在读取下一行。

保持空间(hold space):保持空间是另外一个缓冲区,用来存放临时数据。sed 可以在保持空间和模式空间和模式空间交换数据,但是不能在保持空间上执行普通的 sed 命令。每次循环读取数据过程中,模式空间的内容都会被清空,然而保持空间的内容则保持不变,不会在循环中被删除。(保持空间默认有换行符,文件里有几行内容就有几行换行符)

在使用一般命令时(即那些不涉及到多行处理的命令)只会用到模式空间,sed将处理的行读入模式空间,脚本中的“sed command(sed命令)”就一条接着一条进行处理,直到脚本执行完毕。然后该行被输出,模式被清空(保持空间中的内容仍然存在);接着,在重复执行刚才的动作,文件中的新的一行被读入,直到文件处理完毕。

一般命令执行过程

sedsed 帮助查看sed命令执行过程

sedsed简介

sedsed是用python(python2)写的一个脚本文件

sedsed命令能帮助查看在sed命令执行过程中, 模式空间和保存空间中存储的内容;

安装

  1. 先用echo $PATH获取设备调用命令相关的目录路径;
  2. cd到其中一个目录中去;
  3. 下载sedsed:wget http://aurelio.net/projects/sedsed/sedsed-1.0 -O sedsed
  4. 因为现在linux中默认安装的python版本大都是python3, 所以要把python2安装一下;yum(apt) install python2;  安装后which一下, 获取python2调用路径;
  5. 修改sedsed文件第一行, 改为:#!/usr/bin/env(第4步获取的路径);
  6. 加执行权限: 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) (里面内容有些是错的)

Linux学习笔记------三剑客sed的用法

sedsed 一个更好理解sed执行过程的工具

GNU sed 4.5 版参考文档全文翻译 各命令和随带20个示例详细解析(六)

最后修改日期: 2023年11月7日

作者

留言

撰写回覆或留言