Linux命令行与Shell脚本编程
第二十章 sed进阶
文章目录
- Linux命令行与Shell脚本编程
- 十.sed进阶
- 10.1.多行命令(nNDP)
- 10.1.1.next命令
- 10.1.1.1.单行next命令n
- 10.1.1.2.合并文本行N
- 10.1.2.多行删除命令D
- 10.1.3.多行打印命令P
- 10.2.保留空间(hHgGx)
- 10.3.排除命令(!)
- 10.4.改变执行流程(bt)
- 10.4.1.分支b
- 10.4.2.测试t
- 10.5.模式替换(&)
- 10.5.1.&符号
- 10.5.2.替换单独的单词-反向引用
- 10.6.脚本中使用sed
- 10.6.1.使用后包装器
- 10.6.2.重定向sed的输出
- 10.7.创建sed实用工具
- 10.7.1.加倍行间距
- 10.7.2.对可能含有空行的文件加倍行间距
- 10.7.3.给文件中行编号
- 10.7.4.打印末尾行-滑动窗口
- 10.7.5.删除行
- 删除连续的空行
- 删除开头的空行
- 删除尾部空行
- 10.7.6.删除HTML标签
十.sed进阶
- 多行命令
- 保留空间
- 排除命令
- 改变执行流程
- 模式替换
- 在脚本中使用sed
- 创建sed实用工具
10.1.多行命令(nNDP)
在数据中查找的短语有可能出现在多行中,每行各包含一部分短语.
如果用普通的sed编辑器命令来处理文本是无法找到这种被分开的短语的.
sed编辑器提供了3个可用于处理多行文本的特殊命令:
- N:加入数据流中的下一行,创建一个多行组进行处理.
- D:删除多行组中的一行.
- P:打印多行组中的一行.
10.1.1.next命令
10.1.1.1.单行next命令n
单行next(n)命令会将数据流中的下一行移入sed编辑器的工作空间(称为模式空间).
模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时保存着待检查的文本.
通常sed编辑器在移动到数据流中的下一行之前,会在当前行中执行完所有定义 吗好的命令,单行next命令改变了这个流程!
仅删除首行之后的空行.使用单行next命令,找到首行之后使用next命令将编辑器移动到下一行,处理下一行数据.
除了首行包含单词Header,所以不会有其他行被删除.
$ cat data1.txt
Header LineData Line #1End of Data Lines
$ sed '/Header/{n ; d}' data1.txt
Header Line
Data Line #1End of Data Lines
10.1.1.2.合并文本行N
多行的next(N)命令将下一行添加到模式空间中已有文本之后,将数据流中的两行文本合并到同一个模式空间中.
文本行之间仍然用换行符分隔,但sed编辑器现在会将两行文本当成一行来处理.
使用N命令将下一行与该行合并,然后用替换命令将换行符(\n)替换成空格,就将两行文本在sed编辑器的输出中合并为一行.
$ cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
$ sed '/First/{ N ; s/\n/ / }' data2.txt
Header Line
First Data Line Second Data Line
End of Data Lines
在数据文件中查找一个可能会分散在两行中的文本短语:
替换命令会在文本文件中查找特定的双词短语.如果短语是在一行中,替换命令直接就能搞定.但如果短语分散在两行中,那么替换命令就没辙了.
$ cat data3.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
Thank you for your cooperation.
$ sed 's/System Admin/DevOps Engineer/' data3.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
用N命令将第一个单词所在行与下一行合并,即使短语内出现了换行,仍然可以查找到该短语.
命令在System和Admin之间用了点号模式(.)来匹配空格和换行符这两种情况.但如果点号匹配的是换行符,则删掉换行符会导致两行被合并成一行.
$ sed 'N ; s/System.Admin/DevOps Engineer/' data3.txt
On Tuesday, the Linux DevOps Engineer group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
防止符号被替换,可以在sed编辑器脚本中用两个替换命令,一个用来处理短语出现在多行中的情况,另一个用来处理短语出现在单行中的情况.
$ sed 'N
> s/System\nAdmin/DevOps\nEngineer/
> s/System Admin/DevOps Engineer/
> ' data3.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All DevOps Engineers should attend.
Thank you for your cooperation.
N命令在执行sed编辑器命令前将下一行文本读入模式空间.
当抵达最后一行文本时,没有下一行可读,N命令会叫停sed编辑器.
如果要匹配的文本正好在最后一行,那么命令就无法找到要匹配的数据:
$ cat data4.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
$ sed 'N
> s/System\nAdmin/DevOps\nEngineer/
> s/System Admin/DevOps Engineer/
> ' data4.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All System Admins should attend.
将单行编辑命令放到N命令前面,将多行编辑命令放到N命令后面,解决问题.
$ sed '
> s/System Admin/DevOps Engineer/
> N
> s/System\nAdmin/DevOps\nEngineer/
> ' data4.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All DevOps Engineers should attend.
10.1.2.多行删除命令D
单行删除命令会在不同的行中查找单词System和Admin,然后在模式空间中将两行都删掉.
$ sed 'N ; /System\nAdmin/d' data4.txt
All System Admins should attend.
sed编辑器提供了多行删除(D)命令,只会删除模式空间中的第一行,(删除该行中的换行符及其之前的所有字符)
$ sed 'N ; /System\nAdmin/D' data4.txt
Admin group meeting will be held.
All System Admins should attend.
第二行虽然被N命令加入了模式空间,但仍然完好.
如果需要删除目标数据字符串所在行的前一行,那么D命令符合需要.
$ cat data5.txtHeader Line
First Data LineEnd of Data Lines
$ sed '/^$/{N ; /Header/D}' data5.txt
Header Line
First Data LineEnd of Data Lines
脚本会查找空行,然后用N命令将下一行加入模式空间.
如果模式空间中含有单词Header,则D命令会删除模式空间中的第一行.
10.1.3.多行打印命令P
多行打印命令(P)只打印模式空间中的第一行,即打印模式空间中换行符及其之前的所有字符.
当用-n选项来抑制脚本输出时,和显示文本的单行p命令的用法大同小异.
$ sed -n 'N ; /System\nAdmin/P' data3.txt
On Tuesday, the Linux System
出现多行匹配时,P命令只打印模式空间中的第一行.
P命令的强大之处体现在其和N命令及D命令配合使用的时候.
D命令的独特之处在于其删除模式空间中的第一行之后,会强制sed编辑器返回到脚本的起始处,对当前模式空间中的内容重新执行此命令(D命令不会从数据流中读取新行).
在脚本中加入N命令,就能单步扫过(single-step through)整个模式空间,对多行进行匹配.
$ cat corruptData.txt
Header Line#
@
Data Line #1
Data Line #2#
@
End of Data Lines#
@
$ sed -n '
> N
> s/#\n@//
> P
> D
> ' corruptData.txt
Header Line
Data Line #1
Data Line #2
End of Data Lines
sed将Header Line#行载入模式空间,然后用N命令载入第二行(@)附加到模式空间内的第一行之后.
替换命令(s/#\n@//)用空值替换来删除违规数据(#\n@),
P命令只打印模式空间中已经清理过的第一行.
D命令将第一行从模式空间中删除,并返回到脚本的开头.
下一个N命令将第三行(Data Line #1)文本读入模式空间,继续进行编辑循环.
10.2.保留空间(hHgGx)
模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时保存着待检查的文本.
保留空间(hold space)缓冲区.在处理模式空间中的某些行时,可以用保留空间临时保存部分行.
命令 | 描述 |
---|---|
h | 将模式空间复制(替换)到保留空间 |
H | 将模式空间附加到保留空间 |
g | 将保留空间复制(替换)到模式空间 |
G | 将保留空间附加到模式空间 |
x | 交换附加空间和保留空间的内容同 |
通常,在使用h命令或H命令将字符串移入保留空间后,最终还是要用g命令、G命令或x命令将保存的字符串移回模式空间.
演示如何用h命令和g命令在缓冲空间之间移动数据:
- sed脚本使用正则表达式作为地址,过滤出含有单词First的行.
- 当出现含有单词First的行时,{}中的第一个命令h会将该行复制到保留空间.(此时模式空间和保留空间中的内容一致)
- p命令会打印出模式空间的内容(First Data Line),(被复制进保留空间中的那一行)
- n命令会提取数据流中的下一行(Second Data Line),将其放入模式空间.(模式空间和保留空间的内容不同)
- p命令会打印出模式空间的内容(Second Data Line).
- g命令会将保留空间的内容(First Data Line)放回模式空间,替换模式空间中的当前文本.(模式空间和保留空间的内容相同)
- p命令会打印出模式空间的当前内容(First Data Line).
$ cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
$ sed -n '/First/ {
> h ; p ;
> n ; p ;
> g ; p }
> ' data2.txt
First Data Line
Second Data Line
First Data Line
通过保留空间来回移动文本行,可以强制First Data Line输出在Second Data Line之后.如果去掉第一个p命令,则可以将这两行以相反的顺序输出:
$ sed -n '/First/ {
> h ;
> n ; p
> g ; p }
> ' data2.txt
Second Data Line
First Data Line
10.3.排除命令(!)
排除命令(!)指示命令不应用于数据流中的特定地址或地址区间.
排除(negate)命令(!)是让原本会起作用的命令失效.
$ sed -n '/Header/!p' data2.txt
First Data Line
Second Data Line
End of Data Lines
正常的p命令只打印文件中包含单词Header的行.加了排除命令后,除了包含单词Header的行,其他行都被打印.
sed编辑器的N命令无法处理数据流中的最后一行文本,使用 $!,当sed编辑器读取到最后一行时,不执行N命令,但会对所有其他行执行N命令.
$ cat data4.txt
On Tuesday, the Linux System
Admin group meeting will be held.
All System Admins should attend.
$ sed 'N;
> s/System\nAdmin/DevOps\nEngineer/
> s/System Admin/DevOps Engineer/
> ' data4.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All System Admins should attend.
$ sed '$!N;
> s/System\nAdmin/DevOps\nEngineer/
> s/System Admin/DevOps Engineer/
> ' data4.txt
On Tuesday, the Linux DevOps
Engineer group meeting will be held.
All DevOps Engineers should attend.
反转文档的行:
$ cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
$ sed -n '{1!G ; h ; $p }' data2.txt
End of Data Lines
Second Data Line
First Data Line
Header Line
- 1!G 除第一行外将保留空间的内容追加到模式空间,否则会在处理第一行时,将保留空间的内容(空行)附加到模式空间之后.
$ sed -n '{G ; h ; $p}' data2.txt End of Data Lines Second Data Line First Data Line Header Line
- h 将模式空间的内容复制到保留空间.
- $p 最后一行时,打印内容,否则,每行处理事都会打印模式空间内容.
# sed -n '{G ; h ; p}' data2.txt Header LineFirst Data Line Header LineSecond Data Line First Data Line Header LineEnd of Data Lines Second Data Line First Data Line Header Line
有一个现成的bash shell命令:tac命令会以倒序显示文本文件.
10.4.改变执行流程(bt)
sed编辑器大部分命令会从脚本的顶部开始,一直执行到脚本的结尾(D命令强制sed编辑器在不读取新行的情况下返回到脚本的顶部).
sed编辑器提供了一种方法,可以改变脚本的执行流程,其效果与结构化编程类似.
10.4.1.分支b
分支b命令 基于地址、地址模式或地址区间排除一整段命令.允许只对数据流中的特定行执行部分命令.
[address]b [label]
address参数决定了哪些行会触发分支命令。
label参数定义了要跳转到的位置。无label参数时,跳过触发分支命令的行,处理余下的文本行!
文本行中出现了address匹配的行,程序跳到标签为label的脚本行执行脚本.
如果文本行不匹配分支address,则sed编辑器会继续执行脚本中的命令,包括分支标签后的命令。
定义label参数,指定分支命令要跳转到的位置。标签以冒号开始,最多可以有7个字符:
:label2
示例:
$ cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
$ sed '{2,3b ;
> s/Line/Replacement/}
> ' data2.txt
Header Replacement
First Data Line
Second Data Line
End of Data Replacements
分支命令在数据流中的第二行和第三行处跳过了两次替换命令.
标签:
$ sed '{/First/b jump1 ;
> s/Line/Replacement/
> :jump1
> s/Line/Jump Replacement/}
> ' data2.txt
Header Replacement
First Data Jump Replacement
Second Data Replacement
End of Data Replacements
匹配到address的行,只执行指定标签之后的脚本.
跳转到分支命令前的标签:
$ echo "This, is, a, test, to, remove, commas." |
> sed -n {'
> :start
> s/,//1p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
每次迭代都会删除文本中的第一个逗号并打印字符串。
脚本有一个问题:永远不会结束,形成了一个死循环,不停地查找逗号,直到使用Ctrl+C组合键发送信号,手动停止脚本。
为分支命令指定一个地址模式。如果模式不匹配,就不会再跳转.
$ echo "This, is, a, test, to, remove, commas." |
> sed -n {'
> :start
> s/,//1p
> /,/b start
> }'
10.4.2.测试t
测试命令(t)格式与分支命令相同,但不根据address进行跳转,而是根据 先前替换命令的结果 跳转到某个label处.
跟分支命令一样,没有指定label的情况下,如果测试成功,sed会跳转到脚本结尾。
测试命令提供了一种低成本的方法来对数据流中的文本执行基本的if-then语句:
$ sed '{s/First/Matched/ ; t
> s/Line/Replacement/}
> ' data2.txt
Header Replacement
Matched Data Line
Second Data Replacement
End of Data Replacements
如果匹配了行中的模式,就替换文本,而且测试命令因为没有指定label会跳过后面的替换命令。
如果第一个替换未能匹配,则执行第二个替换命令。
替换命令也能避免分支命令形成的死循环:
$ echo "This, is, a, test, to, remove, commas." |
> sed -n '{
> :start
> s/,//1p
> t start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
当没有逗号可替换时,测试命令不再跳转,而是继续执行剩下的脚本.
10.5.模式替换(&)
在使用模式时,很难知道到底匹配了哪些文本。
如果想为行中匹配的单词加上引号。
$ echo "The cat sleeps in his hat." |
> sed 's/cat/"cat"/'
The "cat" sleeps in his hat.
那么如何在模式中用点号(.)来匹配多个单词?
$ echo "The cat sleeps in his hat. " |
> sed 's/.at/".at"/g'
The ".at" sleeps in his ".at".
用于替换的字符串无法指定点号已匹配到的字符。(造成cat->“.at”,理想结果为cat->“cat”)
10.5.1.&符号
&符号可以代表替换命令中的匹配模式。不管模式匹配到的是什么样的文本,都可以使用&符号代表这部分内容.
$ echo "The cat sleeps in his hat." |
> sed 's/.at/"&"/g'
The "cat" sleeps in his "hat".
10.5.2.替换单独的单词-反向引用
&符号代表替换命令中指定模式所匹配的字符串。但有时候,你只想获取该字符串的一部分.
sed编辑器使用圆括号来定义替换模式中的子模式。随后使用特殊的字符组合来引用每个子模式匹配到的文本.(类似正则表达式中的反向引用)
反向引用由反斜线和数字组成。数字表明子模式的序号,第一个子模式为\1,第n个子模式为\n,以此类推。
在替换命令中使用圆括号时,必须使用转义字符,表明不是普通的圆括号,而是用于划分子模式。与转义其他特殊字符正好相反。
示例:
$ echo "The Guide to Programming" | sed 's/ \(Guide to\) Programming/ \1 Devops/'
The Guide to DevOps
替换命令将"Guide To"放入圆括号标示为一个子模式。使用\1来提取子模式匹配到的文本。
如果需要用一个单词来替换一个短语,而单词刚好又是该短语的子串,在子串中用到了特殊的模式字符,这时使用子模式会方便很多:
$ echo "That furry cat is pretty." |
> sed 's/furry \(.at\)/\1/'
That cat is pretty.
$ echo "That furry hat is pretty." |
> sed 's/furry \(.at\)/\1/'
That hat is pretty.
当需要在两个或多个子模式间插入文本时,反向引用尤其有用。使用子模式在大数中插入逗号:
$ echo "1234567" | sed '{
> :start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start}'
1,234,567
第一个子模式是以数字结尾的任意长度的字符串。第二个子模式是3位数字.
如果匹配到了相应的模式,就在两者之间加一个’,'.
10.6.脚本中使用sed
10.6.1.使用后包装器
编写sed编辑器脚本的过程很烦琐.可以将sed编辑器命令放入shell脚本包装器,就不用每次使用时重新键入整个脚本。
将普通的shell变量及命令行参数和sed编辑器脚本一起使用
$ cat reverse.sh
#!/bin/bash
# 用于sed编辑器脚本的外壳包装器,用于反转测试文件行
sed -n '{1!G; h; $p}' $1
exit
reverse.sh的shell脚本用sed编辑器脚本来反转数据流中的文本行。脚本通过位置变量$1获取第一个命令行参数(要进行反转的文件名):
$ cat data2.txt
Header Line
First Data Line
Second Data Line
End of Data Lines
$ ./reverse.sh data2.txt
End of Data Lines
Second Data Line
First Data Line
Header Line
10.6.2.重定向sed的输出
可以在shell脚本中通过各种标准方法重定向sed编辑器的输出。
可以用$()将sed编辑器命令的输出重定向到一个变量中,以备后用。
使用sed脚本为数值计算结果添加逗号:
$ cat fact.sh
#!/bin/bash
# sed,计算阶乘,并使用逗号格式化结果。
factorial=1
counter=1
number=$1
while [ $counter -le $number ]
dofactorial=$[ $factorial * $counter ]counter=$[ $counter + 1 ]
done
result=$(echo $factorial |
sed '{
:start
s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
t start
}')
echo "The result is $result"
exit
$ ./fact.sh 20
The result is 2,432,902,008,176,640,000
10.7.创建sed实用工具
常见的数据处理sed工具.
10.7.1.加倍行间距
向文本文件的行间插入空行的简单sed脚本
$ sed '$!G' data2.txt
Header LineFirst Data LineSecond Data LineEnd of Data Lines
思路: 将保留空间的默认空行附加到模式空间之后创建空行.
10.7.2.对可能含有空行的文件加倍行间距
如果文本文件已经有一些空行,想给所有行加倍行间距.
思路: 首先删除数据流中的所有空行,然后用G命令在每行之后插入新的空行。
$ cat data6.txt
Line one.
Line two.Line three.
Line four.
$ sed '/^$/d ; $!G' data6.txt
Line one.Line two.Line three.Line four.
10.7.3.给文件中行编号
$ sed '=' data2.txt
1
Header Line
2
First Data Line
3
Second Data Line
4
End of Data Lines
N命令合并行将编号展示在内容的同一行.
通过管道将=命令后的输出传给另一个sed编辑器脚本,由后者使用N命令来合并这两行。还需使用替换命令将换行符更换成空格或制表符
$ sed '=' data2.txt | sed 'N; s/\n/ /'
1 Header Line
2 First Data Line
3 Second Data Line
4 End of Data Lines
$ cat -n data2.txt1 Header Line2 First Data Line3 Second Data Line4 Thired Date Line5 Four6 End of Data Lines
$ nl data2.txt1 Header Line2 First Data Line3 Second Data Line4 Thired Date Line5 Four6 End of Data Lines
类似nl与cat -n选项.但是会引入一些额外的(可能是不需要的)间隔.
10.7.4.打印末尾行-滑动窗口
$ sed -n '$p' data2.txt
End of Data Lines
首先检查当前行是否为数据流中的最后一行。
如果是,则退出命令会停止循环,N命令会将下一行附加到模式空间中的当前行之后。
如果当前行在第5行之后,则6,$D命令会删除模式空间中的第1行。
因此在模式空间中创造了滑动窗口的效果。只会显示文件最后5行。
$ cat data7.txt
Line1
[...] #略
Line15
$ sed '{
> :start
> $q ; N ; 6,$D
> b start
> }' data7.txt
Line11
Line12
Line13
Line14
Line15
封装一下:
$ cat sedtail.sh
#!/bin/bash
if [ $# -ne 2 ]; thenecho "请提供正确的参数:第一个参数为要输出的最后 n 行,第二个参数为文档名称。"exit 1
fi
line=$(($1 + 1))
if ! [[ $line =~ ^[0-9]+$ ]]; thenecho "请输入一个有效的整数作为第一个参数。"exit 1
fi
if [ ! -f $2 ]; thenecho "找不到指定的文件,请提供存在的文件名作为第二个参数。"exit 1
fi
sed "{:start;\$q;N;${line},\$D;b start;}" "$2"
10.7.5.删除行
sed编辑器选择性地删除空行.
删除连续的空行
删除连续空行的最简单方法是用地址区间来检查数据流。
删除连续空行的关键在于创建包含一个非空行和一个空行的地址区间。
匹配了区间,不会删除区间内的行,而是删除不属于该区间的行(两个或更多的空行)。
区间是/./到/^$/。
区间的开始地址会匹配任何至少含有一个字符的行。
区间的结束地址会匹配一个空行。
区间内的行不会被删除。
/./,/^$/!d
文件的数据行之间出现了多少空行,在输出中只保留行间的一个空行。
$ cat data8.txt
Line one.Line two.Line three.Line four.
$ sed '/./,/^$/!d' data8.txt
Line one.Line two.Line three.Line four.
删除开头的空行
删除数据流起始处的空行.
$ cat data9.txtLine one.Line two.
$ sed '/./,$!d' data9.txt
Line one.Line two.
删除尾部空行
$ cat data10.txt
Line one.
Line two.$ sed '{
> :start
> /^\n*$/{$d; N; b start}
> }' data10.txt
Line one.
Line two.
在正常的脚本花括号内还有花括号。这可以在整个命令脚本中将部分命令分组。
命令分组会被应用于指定的地址模式。该地址模式能够匹配只含一个换行符的行。
如果找到了这样的行,而且还是最后一行,删除命令就会将它删除。
如果不是最后一行,那么N命令会将下一行附加到它后面,然后分支命令会跳到循环起始位置重新开始。
10.7.6.删除HTML标签
标准的HTML Web页面包含各种HTML标签,用以标明正确显示页面信息所需要的格式化功能。
$ cat data11.txt
<html>
<head>
<title>This is the page title</title>
</head>
<body><p>This is the <b>first</b> line in the Web page.This should provide some <i>useful</i>information to use in our sed script.
</body>
</html>
$ sed 's/<.*>//g' data11.txtThis is the line in the Web page.
This should provide some
information to use in our sed script.
‘s/<.*>//g’ 只要有文本出现在HTML标签中(比如first),sed脚本就会删除整个文本。
让sed编辑器忽略任何嵌入原始标签中的大于号,可以使用字符组来排除大于号.
$ sed 's/<[^>]*>//g' data11.txtThis is the page titleThis is the first line in the Web page.
This should provide some useful
information to use in our sed script.$ sed 's/<[^>]*>//g ; s/^ *// ; /^$/d' data11.txt #删除空行,以及行首空格
This is the page title
This is the first line in the Web page.
This should provide some useful
information to use in our sed script.