GNU sed 流编辑器
GNU sed 一个流编辑器(十)
标签:Linux sed 4.5版本 帮助文档 参考文档 全文翻译 随带20个示例 详细解析
版权声明:本文为博主原创译文,未经博主允许不得转载。https://blog.csdn.net/qq_39785418/article/details/90295778
7、 20个脚本示例
7.12 字符数统计
这个脚本演示另一种使用sed进行算术运算的方法。在这种情况下,我们必须考虑比较大的数字,因此通过连续的递增来实现这一点是不可行的(而且可能比这个脚本更复杂)。
该方法是将字符数量与相应字母相互映射,这是一种使用sed实现的算盘。‘a’代表个位数,‘b’代表十位数,依次类推:我们只需关心当前行中的字符数量n,然后以n+1个‘a’加入,然后把10个‘a’转换成一个‘b’,实现个位到十位的进位,以此类推,传播到百位、千位等等。
通常,运行结果保留在保持空间中。
在最后一行,我们将算盘形式转换回十进制。为了程序实现多样性,这里通过一个循环完成,而不是用80个s命令完成(有些sed的实现要求每个脚本的命令总数至多199个,不能超过这个限制。):首先我们转换个位数,且从中删除“a”;然后我们转动字母,使十位数“b”变成“a”,依此类推,直到没有字母为止。
$ vim wc-c.sed
#!/usr/bin/sed -nf
# 只关心输入行的字符数量n,下面实现在保持空间追加 n+1 个由a组成的序列。
# s命令实现n个全部由a组成的序列,例如n=3,成“aaa”,
# H命令在保持空间中追加1个换行符和模式空间的序列,
# 成“...\naaa”,然后交换空间,并把"\n"也替换成a,所以增加了n+1个a。
s/./a/g
H
x
s/\n/a/
# 下段代码实现向上进位。t和b命令可以加快执行速度。
# 如果从左面开始出现了连续的10个a序列,替换为1个b,
# 则跳转到执行10个b替换为c的操作,如果成功继续下一语句,…
# 否则,跳转到标签done处。
t a
:a; s/aaaaaaaaaa/b/g; t b; b done
:b; s/bbbbbbbbbb/c/g; t c; b done
:c; s/cccccccccc/d/g; t d; b done
:d; s/dddddddddd/e/g; t e; b done
:e; s/eeeeeeeeee/f/g; t f; b done
:f; s/ffffffffff/g/g; t g; b done
:g; s/gggggggggg/h/g; t h; b done
:h; s/hhhhhhhhhh//g
# 如果没有到达最后一行,h命令会用模式空间替换保持空间,b命令进入下一轮循环。
# 前面的执行过程简单举例说明如下:
# 例如第一次读入3个字符,“aaaa”,第二次读入7个字符,“baa”,...
# “bbbbbbaa”,... ,“cbbaa”
:done
$! {
h
b
}
# 如果程序执行到此,说明已经到达最后一输入行。转换回十进制。
# 如果模式空间中没有字符a,在最后添加一个0
# 否则,按照a的数量替换成相应的数量
:loop
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/
:next
# 程序执行到此,模式空间中所有位于后部的a替换成相应的数字,
# y把b-h转换成a-g,如果还有a-h字符,流程跳转到loop处,
# 继续替换工作,直到字母无为止,这时已经是字符型的数量,最后打印结果。
# 文本“cbbaa”的过程为:cbbaa-->cbb2-->baa2-->b22-->a22-->122
y/bcdefgh/abcdefg/
/[a-h]/ b loop
P
$ chmod +x wc-c.sed
$ wc -c cat-n.data
34 cat-n.data
$ ./wc-c.sed cat-n.data
34
7.13 单词数统计
此脚本几乎与前一个脚本相同,一旦遇到行中的每个单词都转换为单个“a”(在前一个脚本中,每个字符都更改为“a”)。
有趣的是,真正的wc程序已经为“wc-c”优化了循环,因此它们在计算单词时的速度比计算字符要慢得多。相反,这个脚本的瓶颈是算术,因此字数计算速度更快(它必须管理较小的数字)。
同样,通用部分没有注释,从而体现对sed脚本注释的重要性。
#!/usr/bin/sed -nf
# wc-w.sed
# 把每个单词替换成一个字符a,然后清空空格
# 把当前行所有的1个或多个空格或制表符替换成1个空格,
# 在行前插入一个空格,把所有的单词替换成“a ”,删除所有空格,
# 留下了相应单词数量的a序列。例如“aaa".
s/[ \t]\+/ /g
s/^/ /
s/ [^ ]\+/a /g
s/ //g
# 把新生成的a序列追加到已经保存在保持空间中的序列后面,
# 交换空间,把追加的换行符出掉。
H
x
s/\n//
# From here on it is the same as in wc-c.sed.
/aaaaaaaaaa/! bx; s/aaaaaaaaaa/b/g
/bbbbbbbbbb/! bx; s/bbbbbbbbbb/c/g
/cccccccccc/! bx; s/cccccccccc/d/g
/dddddddddd/! bx; s/dddddddddd/e/g
/eeeeeeeeee/! bx; s/eeeeeeeeee/f/g
/ffffffffff/! bx; s/ffffffffff/g/g
/gggggggggg/! bx; s/gggggggggg/h/g
s/hhhhhhhhhh//g
:x
$! {h; b;}
:y
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/
y/bcdefgh/abcdefg/
/[a-h]/ by
p
7.14 行数统计
现在没有做奇怪的事情,因为sed免费提供了“wc-l”功能!!!看:
#!/urs/bin/sed -nf
$=
7.15 打印前几行
这个脚本可能是最简单、有用的sed脚本。它显示输入的前10行;显示的行数正好在q命令之前。
#!/usr/bin/sed -f
10q
7.16 打印最后几行
打印最后n行比前面n行要复杂得多,但确实是可能的。n代码编写在感叹号字符之前的第二行。
该脚本与tac脚本类似,也是把最终输出保留在保持空间中,并在最后打印:
#!/usr/bin/sed -nf
# 如果是第一行,保持空间为空,直接执行一条语句;否则,
# H在保持空间中追加一个换行符和当前行,
# g用保持空间替换模式空间。
1! {H; g}
# 第11行开始,会把当前模式空间中的第一个行(含换行符)删除,
# 也就是,后面追加一行,前面删一行,空间中保持10行文本。
1, 10! s/[^\n]*\n//
# 如果到达最后一行打印结果
$p
# 不是最后一行,用模式空间替换保持空间
h
# 这里简单描述一下整个执行过程:
# 前面输入的10行是逐行追加到保持空间中,行与行之间由换行符分隔。
# 转到模式空间后,如果输入流有10行以上,则模式空间中后面加入一行,
# 前面删除一行。如果不是最后一行,转到保持空间,直到最后一行打印结果。
$ chmod +x tail-n.sed
$ seq 50 | ./tail-n.sed
41
42
43
44
45
46
47
48
49
50
主要是脚本保持一个10行的窗口,并通过后面添加一行和删除最前面的行来滑动它(第二行上的替换命令类似于D命令,但不会重新启动循环)。
这种“滑动窗口”技术是编写高效和复杂的sed脚本的一种非常强大的方法,因为像P这样的命令,如果手工实现,需要大量的工作。
为了介绍这项基于N、P和D命令的技术,在本章的剩余部分中会充分加以演示,这里介绍使用简单的“滑动窗口”实现tail的方法。
这看起来很复杂,但实际上它的工作原理和上一个脚本是一样的:当我们插入了适当数量的行之后,我们停止使用保持空间来保持行与行之间的状态,而是使用N和D使模式空间滑动一行:
#!/usr/bin/sed -f
1h
2,10 {H; g}
$q
1,9d
N
D
# 第一输入行保存到保持空间中,后面两条语句不执行,执行语句“1,9d”,
# 删除模式空间,立即开始第二个循环,不会执行N和D命令,
# 从读入的第二输入行开始执行“2,10 {H; g}”这条语句,H追加当前行到保持空间,
# g把追加后的内容备份替换模式空间中,然后执行“1,9d”删除并立即进行下一轮循环,
# 此时保持空间的内容不变。直到到输入第9行为止,第10输入行追加操作后,
# 不再使用保持空间,仅在模式空间进行,此时,窗口中有了10条语句。
# N在没有重启循环情况下,读取下一输入行,D删除窗口中最前面的一行,
# 然后下一轮循环,一直遇到最后输入行时,在q退出程序前,打印结果。
7.17 去重复行
这是使用N、P和D命令的艺术示例,可能是最难掌握的。
#!/usr/bin/sed -f
# 把读入的、放在模式空间中的行保存到保持空间中,备用。
h
:b
# 如果到达最后输入行,打印后退出。
$b
# 读取下一输入行,追加到模式空间中后,模式空间中有以换行符分隔的两行。
N
/^\(.*\)\n\1$/ {
# 程序执行到这里面,说明模式空间中的两行是完全一样的,
# 把保存在保持空间的行取回来,此时模式空间只有一行,
# 跳转标签b,在没有重启新循环下,继续追加下一输入行,
# 继续比较是否一样,如果还是一样,继续跳转,否则执行后面语句。
g
bb
}
# 如果N追加的行是最后输入行,打印并退出。
$b
# 如果模式空间中的两行内容不同,P只打印第一行,
# D删除P打印过的内容,这时模式空间中只有一行,立即开始下一轮循环。
P
D
# 简单讲解一下执行过程。保持空间保存一备用,模式空间中一般有两行,
# 前面行与保持空间一样,第二行是当前输入的,然后比较两行是否一样,
# 如果是一样的,取回备份,这样就相当于忽略了一行,
# 再读入下一输入行进行比较,如果是不同的,则打印空间中前面的行,再
# 删除前面的行,留下后面的行在模式空间中,开始下一轮循环。最后打印退出。
$ chmod +x uniqline.sed
$ cat duplines.data
111
111
222
333
333
333
$ ./uniqline.sed duplines.data
111
222
333
7.18 打印重复行
该脚本只打印重复的行,与“uniq -d”功能类似。
#!/usr/bin/sed -nf
# 输入的只有一行,会执行下面的b命令,退出程序。
$b
# N命令有点特殊,如果模式空间中是空的,一轮循环开始后,会先读入一行,
# 遇到N后追加一行,所以执行D命令后,模式空间有遗留的内容,开始下一轮循环后,
# 只有执行N命令才会追加下一输入行。这是我使用sedsed调试软件后才明确的!!!
N
# 此时模式空间中有两行,如果以换行符分隔的两行完全一样,执行命令组。
/^\(.*\)\n\1$/ {
# 先删除前面的一行及换行符,并打印。
s/.*\n//
p
# 此时模式空间只有一行,N命令追加一行,如果两行一样,
# 删除前面的一行,继续下一输入行比较,直到两行不一致。
:b
$b
N
/^\(.*\)\n\1$/ {
s/.*\n//
bb
}
}
# 此时模式空间内有不一样的两行,且处在最后一输入行,退出。
$b
# 既然两行不一致,删除前面的行,留下后面的行,开始下一轮循环。
D
7.19 删除所有重复行
该脚本只会打印唯一的行,与“uniq -u”功能一致。
#!/usr/bin/sed -f
# 如果输入的只有一行,直接打印并退出。否则,N命令追加一行,
# 如果两行不一样,则P命令打印当前空间中前面的行及其后随的换行符,
# 然后D命令删除打印过的内容。
$b
N
/^\(.*\)\n\1$/ ! {
P
D
}
# 下面可以形成不离开当前sed循环的内部循环。
# 程序执行到此,说明模式空间中有相同的两行,
# 如果到达最后输入行,d删除模式空间,退出,否则,
:c
$d
# 删除空间中前面行及其后面的换行符,追加一行,如果当前的两行又一样,
# 跳转到标签c处,继续前面的所述的操作。如果当前两行不一样,
s/.*\n//
N
/^\(.*\)\n\1$/ bc
# 删除前面的一行及其后面的换行符,这样出现重复过的行都被删除,
# 模式空间中保留着刚追加的行,开始下一轮sed循环。
D
7.20 压缩空行
作为最后一个例子,这里有三个脚本,它们的复杂性和速度都在增加,它们实现了与“cat -s”相同的功能,即压缩重复空白行。
第一个示例功能是,如果输入的开头和结尾有一些空行,在开头行开始每行前面都会有下一空行,结尾的空行全部留下。
#!/usr/bin/sed -f
# 下面地址表达式会搜索有0个或0个以上的换行符——只有空行才会这样。
# (0个说明新循环后读入行是空行,此时没有“\n”),N追加一行,b跳转到x标签处,
# 继续判断模式空间内是否有一个或者多个连续的换行符,没有其他内容,
# 如果是,继续追加下一输入行,并跳转到x处,… ,
# 要么N命令在最后一输入行上继续执行,遇到输入结束,打印空间内容并退出,
# 所以如果文件的最后部分是连续空行的话,会保留这些空行。
# 要么模式空间中不全是换行符,不执行命令组,程序执行后续语句。
:x
/^\n*$/ {
N
bx
}
# 程序到此,说明模式空间中,要么只有一非空行,要么前面几空行,最后一行非空,
# s命令会匹配0个及0个以上的换行符并替换成一个换行符,开始下一轮循环前
# 打印结果,所以,在每一行之前不管有无空行及多少空行,都有一个空行。
s/\n*/\
/
第二个示例有点复杂,在开始时会删除所有空行。如果末尾有空行的话,它会留下一空行。
#!/usr/bin/sed -f
# 地址表达式“1,/^./”会匹配从第一输入行开始,到第一次非空为止,
# 如果匹配成功,进入命令组,对空行(/./!)进行d删除,立即开始下一轮循环。
1,/^./ {
/./! d
}
# 程序执行至此,前导空行已经全部删除。
# 如果当前模式空间非空,则跳过命令组,自动打印内容,开始下一轮循环;
# 如果当前空间中只有空行,N追加下一输入行,如果追加的行也是空行,
# 此时模式空间中只有一个换行符,s会匹配成功并清除这个换行符,
# 程序跳转到标签x位置继续上述操作;如果追加的输入行非空,
# 则自动打印,开始下一轮循环。所以,结果是没有前导空行、从第二行开始的
# 中间空行有一行以上,只会保留一空行,输入最后部分如果有空行也一样。
# 如果行与行之间没有空行,不会加入空行,予以保留。
:x
/./! {
N
s/^\n$//
tx
}
第三个示例将删除前导和尾随空白行。它也是最快的。请注意,循环完全是使用n和b完成的,而不依赖于sed执行到脚本末,自动重新启动脚本开启下一轮循环实现的。
#!/usr/bin/sed -nf
# 删除所有前导的空行
/./! d
# 程序到此,模式空间中有一非空行
:x
# p打印当前行
p
# n读取下一行,替换模式空间
# 如果n遇到输入结束,退出程序
n
# 如果非空,跳转到标签x位置,继续打印 ...
/./ bx
# 程序到此说明当前行是空行,匹配不到任意一个字符
:z
# n读取下一行,替换模式空间,如果这时n遇到输入结束,退出程序,
# 所以尾随的空行不会打印。否则,继续下一语句
n
# 如果也是空行,程序跳转到z位置,忽略当前空行,所以会删除所有空行
/./! bz
# 到此,所有的空行都被删除或忽略,但是当前行是非空行,由于我们要做的
# 是压缩重复行,所以手工插入一空行。程序跳转到x处继续。
i\
bx
8、 GNU sed的局限性和非局限性
对于那些想要编写可移植的sed脚本的人,需要注意,已知有些sed实现对模式空间和保留空间中的行长度限制为不超过4000字节。POSIX标准规定,符合标准的实现应支持至少8192字节的行长度。
GNU sed在行长度上没有内置限制,只要它可以使用malloc()更多(虚拟)内存,就可以根据需要读取或构造行。
但是,递归用于处理子模式和无限重复。这意味着可用的堆栈空间可能会限制特定模式处理的缓冲区的大小。
9、 学习sed的其他资源
有关GNU sed的最新信息,请访问https://www.gnu.org/software/sed/。
可以向sed-devel@gnu.org发送一般问题和建议。访问邮件列表存档以获取过去的讨论,网址为https://lists.gnu.org/archive/html/sed devel/。
以下资源提供了有关sed的信息(包括GNU sed和其他变体)。注意这些不是由GNU sed开发人员维护的。
sed $HOME: http://sed.sf.net
sed FAQ: http://sed.sf.net/sedfaq.html
seder’s grabbag: http://sed.sf.net/grabbag
"sed-user"邮件列表由Sven Guckes负责维护,可以访问http://groups.yahoo.com(注意这不是GNU sed邮件列表)。
10、 报告错误(bug)
请把错误报告发送至bug-sed@gnu.org。此外,如果可能的话,请在报告正文中包含“sed --version”的输出。不要像这样来报告bug:
while builing frobme-1.3.4
$ configure
error sed: file sedscr line 1: Unknown option to ’s’
如果GNU sed没有配有您最喜欢的示例,则需要花费额外的几分钟时间来确定您遇到的特定问题,请您创建一个独立的测试用例。与其他程序(如C编译器)不同,为sed编写这样的测试用例非常简单。
独立测试用例包括执行测试所需的所有数据,以及导致问题的特定sed调用。独立测试用例越小越好,一个测试用例不应该涉及到远离sed的东西,比如“尝试配置frobme-1.3.4”。是的,原则上,这样就有足够的信息来查找bug,但事实上并不容易。
下面是一些通常报告的不是bug的bug。
最后一行上执行N命令
当N命令在最后文件的最后一行上执行,大多数sed版本退出程序,不会打印任何内容。GNU sed退出程序前会打印模式空间内容,
当然除非设置“-n”开关。这是程序设计的选择。
默认行为(gnu扩展,非POSIX规范):
$ seq 3 | sed N
1
2
3
强制服从POSIX规范的行为:
$ seq 3 | sed --posix N
1
2
示例,“sed N foo bar”的行为,取决于foo的行数是偶数还是奇数(这是导致行为改变的实际“bug”)。或者,当您编写一个
脚本来读取地址模式匹配后的几行代码时,传统的sed实现会迫使您编写类似这样的代码
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
而不仅是
/foo/{ N;N;N;N;N;N;N;N;N }
在任何情况下,最简单的应对方法是在脚本中使用“$d;N”,它依赖于传统行为,或者将POSIXLY_CORRECT变量设置为非空值。
regex语法冲突(反斜杠问题)
sed使用POSIX基础正则表达式语法。根据这个标准,一些转义序列的含义在语法上没有定义;在sed上使用时要注意的是:
\|、\+、\?、\‘、\’、\<、\>、\b、\B、\w和\W。
由于使用POSIX基础正则表达式的所有GNU程序,GNU sed把这些转义序列当作特殊字符,所以,“x\+”会匹配一个或多个出现
的“x”。“abc\|def”可匹配“abc”或“def”。
当使用这种语法编写好的脚本,在非GNU sed实现中运行时会导致问题。一些已经编写好sed程序,它假设“\|”和“\+”匹配字面
意义上的“|”和“+”。如果这样的脚本要在GNU sed现代实现版本上运行,必须要对它进行修改,移去谬误的反斜杠。
另一方面,一些脚本使用“s|abc\|def||g”去删除“abc”或“def”,在以前sed版本工作正常,而新版本sed 4.0.x把它解释为
移除字符串“abc|def”。按照POSIX,这又是没有定义的行为,而且这种解释可能更具论证性:例如,老版本sed,要求
正则表达式匹配器在转义斜杠时把“\/”解析为“/”,而这又是没有定义的行为;新版本的行为避免了这种情况,这很好,因为
正则表达式匹配器只有部分在我们的控制之下。
另外,这个版本的sed支持几个转义字符(其中一部分是多字符)在脚本中插入不能打印的字符
(\a、\c、\d、\o、\r、\t、\v、\x)。如果把这些编写在其他sed实现中也会引起相似的问题。
-i 突破只读文件的限制
简而言之,“sed-i”允许您删除只读文件的内容。一般来说,“-i”选项(参见第2章[调用])允许突破文件受保护的限制。
这不是bug,而是Unix文件系统工作方式的结果。
文件上的权限表示该文件中的数据可能发生什么,而目录上的权限表示该目录中的文件列表可能发生什么。“sed -i”永远不会
打开已保存在磁盘上的文件来进行编辑操作。相反,它工作在一个临时文件中,最后这个临时文件重命名为原文件名。如果
重命名或删除文件,实际上是在修改目录的内容,所以这种操作取决于目录的权限,而不是文件的权限。出于同样的原因,
sed不允许在只读目录中的可写文件上使用“-i”,并且当在这样的文件上使用“-i”时,将会破坏硬链接或符号链接。
0行地址不能工作,会导致一个错误
没有输入0行。0是一个特殊的地址,它只在脚本启动时,仅用于将像“0,/RE/”这样的地址当成活动地址。如果您写成
“1,/abc/d”,且第一行包含单词“abc”,那么,该行会被忽略,因为地址范围必须跨越至少两行(除了文件结尾以外);
但您可能希望删除行内有字符串“abc”的每一行,包括第一行,这是用“0, /abc/d”可以实现。
[a-z]不区分大小写
您可能在设置语言环境后会遇到问题。POSIX要求[a-z]使用当前语言环境的排序顺序(collation order)——用C语言来说,
这意味着使用strcoll(3)而不是strcmp(3)。有些语言执行环境具有不区分大小写的排序顺序,而另一些环境则没有。
另一个问题是[a-z]试图使用排序符号(collation symbols)。只有在GNU系统上,使用GNU libc的正则表达式匹配器而不是
编译随GNU sed提供的正则表达式匹配器,才会发生这种情况。例如,在丹麦语言环境中,正则表达式“^[a-z]$”匹配字符串
“aa”,因为“aa”是单个排序符号,位于“a”之后和“b”之前;“ll”'在西班牙语中表现类似,或“ij”在荷兰语中表现类似。
为了解决这些可能导致shell脚本错误的问题,可以将LC_COLLATE和LC_CTYPE环境变量设置为“C”。
s/.*// 不能清空模式空间
如果您的输入流中包含无效的多字节序列,会发生这种情况。POSIX强制不能通过“.”来匹配这种无效序列,所以“s/.*//”
不能如您所愿那样清空模式空间。事实上,在大多数多字节环境中(包括UTF-8设置)、脚本执行过程中,是没有办法清空sed
的缓冲区的。正因如此,GNU sed提供了一个扩展的用以清空的z命令。
为了解决这些可能导致shell脚本错误的问题,可以将LC_COLLATE和LC_CTYPE环境变量设置为“C”。
附录A GNU免费文档许可证
略。
附录:如何将此许可证用于您的文档
略。
概念索引
略。
(全文结束!)
如果您觉得译文对您有帮助,不妨给个 微信打赏 翻译不易,各位的支持,能激发和鼓励我更大的热情。谢谢!