【Linux】sed编辑器二
一、处理多行命令
sed编辑器有3种可用于处理多行文本的特殊命令。
- N:加入数据流中的下一行,创建一个多行组进行处理;
- D:删除多行组中的一行;
- P:打印多行组中的一行。
1、next命令:N
单行next命令
单行next(n)命令会告诉sed编辑器移动到数据流中的下一行,不用再返回到命令列表的最开始位置。通常sed编辑器在移动到数据流中的下一行之前,会在当前行中执行完所有定义好的命令,而单行next命令却不一样。
如下所示,文本test.txt中有两行空行。
我们想只删除第一行空行,执行下面的命令却无法做到,它会将两行空行都删除掉。
sed '/^$/d' test.txt
可以用单行next命令解决此问题:
sed '/成都/{n ; d}' test.txt # d表示删除
先用脚本查找到含有“成都”的那一行,然后,单行next命令(n)会让sed编辑器移动到文本的下一行,也就是我们想要删除的空行,接着,sed编辑器继续执行命令列表中的命令,即使用删除命令(d) 删除空行。sed编辑器在执行完命令脚本后会读取数据流中的下一行文本,并从头开始执行脚本,但它却找不到包含“成都”的行了,所以就不会再删除其它行。
合并文本行
单行next(n)命令会将数据流中的下一行移入sed编辑器的工作空间(模式空间)。多行版本的next(N)命令则是将下一行添加到模式空间中已有文本之后。这样就会将数据流中的两行文本合并到同一个模式空间中,文本行之间仍然用换行符分隔,但sed编辑器会将两行文本当成一行来处理。
模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时保存着检查的文本。
sed '/重庆/{N ; s/\n/ / }' test.txt
sed编辑器首先找到含有“重庆”的行,找到后,使用N命令将下一行与该行合并,接着使用替换命令s将换行符(\n)替换成空格。如此一来,两行文本就会成为一行后输出。
在数据文件中找到一个可能会分散在两行中的文本短语。
如下所示,电信和诈骗园区之间的 . 是用来匹配空格和换行符的,但如果它匹配到了换行符,就会删掉换行符,从而导致两行合并成一行。
可以使用两个替换命令解决上面两行合并成一行的问题。
第一个替换命令用来处理短语出现在单行中的情况,第二个替换命令用来处理短语出现在多行中的情况。
2、多行删除命令:D
sed编辑器中的多行删除命令(D)只会删除模式空间中的第一行,即删除该行中的换行符及其之间的所有字符。
删除目标数据字符串所在行的前一行。
sed '/^$/{N ; /缅北/D}' test.txt
sed编辑器首先会查找空行,然后用N命令将下一行加入模式空间,如果模式空间中有含有“缅北”的词语,那么D命令就会删除模式空间中的第一行。
3、多行打印命令:P
多行打印命令(P)只打印模式空间中的第一行,即打印模式空间中的换行符及其之前所有字符。当用-n选项来抑制脚本输出时,它就和显示文本的单行p命令的用法差不多。
如下所示,当出现多行匹配时,P命令只会打印模式空间中的第一行。此命令的强大之处在于其和N命令及D命令配合使用之时。
D命令的独特之处在于其删除模式空间中的第一行之后,会强制sed编辑器返回到脚本的起始处,对当前模式空间中的内容重新执行此命令(D命令不会从数据流中读取新行)。在脚本中加入N命令,就能单步扫过整个模式空间,对多行进行匹配。
sed -n '
N
s/#\n@//
P
D
' test.txt
整个过程是:先用sed将第一行载入模式空间,然后用N命令载入第二行(@),并将其附加到模式空间内的第一行之后。替换命令用空值替换来删除违规的数据(#\n@),然后P命令只打印模式空间中已经清理过的第一行。D命令将第一行从模式空间中删除,并返回到脚本的开头,下一个命令就将第三行文本读入模式空间,继续编辑循环。
二、保留空间
sed编辑器有另一块称作为保留空间(hold space)的缓冲区。当用户在处理模式空间中的某些行时,可以用保留空间临时保存部分行。
h | 将模式空间复制到保留空间 |
H | 将模式空间附加到保留空间 |
g | 将保留空间复制到模式空间 |
G | 将保留空间附加到模式空间 |
x | 交换模式空间和保留空间的内容 |
通常,在使用h命令或H命令将数据移入保留空间后,最终还是要用g命令、G命令或x命令将保存的数据重新移回模式空间,否则,一开始就不用考虑保存的问题。
sed -n '/缅北/{
> h ; p ;
> n ; p ;
> g ; p }
> ' test.txt
整个过程如下:
- sed使用正则表达式过滤出含有“缅北”的行;
- 当出现“缅北”的行时,{ }中的第一个命令 h 会将该行复制进保留空间。这是,模式空间和保留空间中的内容是一样的。
- p 命令会打印出模式空间的内容(缅北有电信 #1),也就是被复制进保留空间中的那一行。
- n 命令会提取数据流中的下一行(诈骗园区……),将其放入模式空间。现在,模式空间和保留空间的内容就不一样了。
- p 命令会打印出模式空间的内容(诈骗园区……)
- g 命令会将保留空间的内容(缅北有电信 #1)返回模式空间,替换模式空间中的当前文本。模式空间和保留空间的内容又相同了。
- p 命令会打印出模式空间的当前内容(诈骗园区……)
以相反的顺序输出:
sed -n '/缅北/{
> h ;
> n ; p ;
> g ; p }
> ' test.txt
三、排除命令
感叹号(!)命令用于排除(negate)命令,让原本会起作用的命令失效。
可以结合保留空间实现反转数据流中文本行的先后顺序。太复杂不过多展示。
bash shell中有 tac 命令,可以以倒序显示文本文件。
四、改变执行流程
通常来说,sed编辑器会从脚本的顶部开始,一直执行到脚本的结尾(D命令例外,它会强制sed编辑器在不读取新行的情况下返回到脚本的顶部)
1、分支命令:b
格式:[address]b [label]
- address:决定哪些行会触发分支命令
- label:定义要跳转的位置。如果没有label参数,则跳过触发分支命令的行,继续处理余下的文本行。
sed '{2,3b;
> s/line/replacement/}
> ' test.txt
分支命令跳过了第二行和第三行的替换命令,只替换了第一行和第四行。
如果不想跳到脚本末尾,可以定义label参数,指定分支命令要跳转到的位置。标签以冒号开始,最多可以有7个字符,将其放在分支命令之后。
sed '{/first/b jump1;
> s/line/replacement/
> :jump1
> s/line/Jump replacement/}
> ' test.txt
如下,分支命令指定,如果文本行中出现了first,则程序就应该跳到标签为jump1的脚本行,如果文本行不匹配分支address,则sed编辑器就会继续执行脚本中的命令,包括分支标签jump1之后的命令。因此,两个替换命令s都会被应用于不匹配分支address的行。
如果某行匹配分支address,那么sed编辑器就会跳转到带有分支标签jump1的那一行,因此只有最后一个替换命令会被执行。
如下例子演示了跳转到sed脚本下方的标签:
2、测试命令:t
测试命令也可以改变sed编辑器脚本的执行流程。它会根据先前替换命令的结果跳转到某个label处,而不是根据address进行跳转。
如果替换命令成功匹配并完成了替换,测试命令就会跳转到指定的标签。如果替换命令未能匹配指定的模式,测试命令就不会跳转。
格式:[address]t [labe1]
在没有指定label的情况下,如果测试成功,sed就会跳转到脚本结尾。
sed '{s/first/matched/ ; t
> s/line/replacement/}
> ' test.txt
第一个替换命令会先查找first,如果匹配了行中的模式,就替换文本,而且测试命令会跳过后面的替换命令。如果第一个替换未能匹配,则执行第二个替换命令。 (注意,这里说的是第二行的first替换成matched后,就不会再执行替换第二行的line了,而不是指不执行后面文本行的替换)
五、模式替换
1、&符号
&符号可以代表替换命令中的匹配模式,不管匹配到什么样的文本,都可以使用此符号代表这部分内容。
使用点号匹配at前面的那个字符,然而,用于替换的字符串“.at”无法指定点号已匹配到的字符cat和hat。
使用&符号可以解决该问题:当匹配到单词cat,“cat”就会成为替换后的单词;当匹配到单词hat,“hat”就会成为替换后的单词。
2、替换部分
&符号虽然能代表替换命令中指定模式所匹配的字符串。但有时候,用户只想获取该字符的部分。
sed编辑器使用圆括号来定义替换模式中的子模式,随后使用特殊的字符组合来引用每个子模式匹配到的文本。反向引用由反斜线和数字组成,数字表明子模式的序号,第一个子模式为\1,第二个子模式为\2,以此类推。
在替换命令中使用圆括号时,必须使用转义字符\,用此表明这不是普通的圆括号,而是用于划分子模式的。这跟转义其他特殊字符正好相反。
echo "The Guide to Programming" | sed 's/\(Guide to\) Programming/\1 DevOps/'
此命令将Guide to放入圆括号,将其标示为一个子模式,然后使用\1来提取该子模式匹配到的文本。
如果需要用一个单词替换一个短语,而此单词正好又是该短语的子串:
ehco "That furry cat is pretty." | sed 's/furry \(.at\)/\1/'
六、在脚本中使用sed
1、包装器
可以将sed编辑器命令放入shell脚本包装器,这样就不用每次使用时都重新输入整个脚本。包装器充当的是sed编辑器脚本和命令行之间的中间人角色。
#!/bin/bash
#将sed编辑器放入shell脚本包装器
#反转文件中的内容
sed -n '{1!G; h; $p}' $1
exit
2、重定向sed的输出
默认情况下,sed编辑器会将shell脚本的结果输出到STDOUT。可以使用 $() 将sed编辑器命令的输出重定向到一个变量中。
#!/bin/bash
# 对sed编辑器使用shell包装器
# 计算阶乘并用逗号格式化结果
factorial=1
counter=1
number=20
while [ $counter -le $number ]
do
factorial=$[ $factorial * $counter ]
counter=$[ $counter + 1 ]
done
result=$(echo $factorial |
sed '{
:start
s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
t start
}')
echo "阶乘的结果是 $result"
exit
七、sed实用工具
1、加倍行间距
sed 'G' test.txt
G命令只是将保留空间内容附加到模式空间内容之后,当启动sed编辑器时,保留空间只有一个空行。将它附加到已有行之后,就创建出了空行。
但是最后一行也有空行。
使用排除符号(!)和行尾符号($)确保脚本不会将空行附加到数据流的最后一行之后。
sed '$!G' test.txt
2、对可能已含有空行的文件加倍行间距
对于文本中可能已经含有空行的数据,先要将已有的空行删除,删除空行需要使用 d 命令和一个匹配空行的模式:/^$/d;然后用 G 命令在每行之后插入新的空行。
sed '/^$/d ; $!G' test.txt
3、给文件中的行编号
可以使用等号 = 显示文本中的行号
sed "=" test.txt
在获得 等号命令 的输出之后,可以通过管道符将输出传给另一个sed编辑器,由后者使用 N命令合并行号和数据行,急着使用 替换命令s 将换行符更换成空格或制表符。
sed "=" test.txt | sed 'N; s/\n/ /'
有些bash shell命令也可以查看行号
cat -n test.txt
nl test.txt
4、打印末尾行
……
打印末尾的10行数据
sed '{
:start
$q ; N ; 11,$D
b start
}' test.txt
5、删除空行
删除连续的空行
删除连续空行的关键在于创建包含一个非空行和一个空行的地址区间。如果sed编辑器遇到了这个区间,它不会删除行。但对于不属于该区间的行(两个或更多的空行),则执行删除操作。
如下所示,脚本中指定的区间是/./到/^$/。区间的开始地址会匹配任何至少含有一个字符的行,区间的结束地址会匹配一个空行,在这个区间的行不会被删除。
sed '/./,/^$/!d' test.txt
删除开头的空行
如下所示,脚本用地址区间来决定要删除哪些行。这个区间从含有字符的行开始,一直到数据流结束,在这个区间内的任何行都不会从输出中删除,即含有字符的第一行之前的任何行都会被删除。
sed '/./,$!d' test.txt
删除结尾的空行
sed '{
:start
/^\n*$/{$d; N; b start }
}' test.txt
删除HTML标签
sed 's/<[^>]*>//g' test1.txt
删除多余的空行,增加 D命令:
sed 's/<[^>]*>//g ; /^$/d' test1.txt