GNU sed 一个流编辑器(七)
标签:Linux sed 4.5版本 帮助文档 参考文档 全文翻译 随带20个示例 详细解析
版权声明:本文为博主原创译文,未经博主允许不得转载。https://blog.csdn.net/qq_39785418/article/details/90080360
7、 20个脚本示例
下面是一些sed脚本,它们将指导您掌握sed的艺术。(注意:这后面的示例中的注释基本上是译者写的,原文注释不多。)
7.1 合并行
这节使用N、D和P命令处理多行,且b和t命令用于分支跳转。参见6.3节[多行技术]和6.4节[分支和流程控制]。
连接特殊的行(例如第2和3行需要被合并):
$ cat lines.txt
hello
hel
lo
hello
$ sed '2{N;s/\n//}' lines.txt
hello
hello
hello
(译者:sed开始后读入第一输入行“hello”,行号1不匹配地址行号2,跳过命令组,默认打印结果;开始第二轮循环,读入第二行“hel”,匹配地址行号2,执行N命令,追加一个换行符和读入的第3输入行“lo”到模式空间,形成“hel\nlo”,s命令删除换行符,然后打印“hello”,开始第三轮循环与第一轮循环一样。)
$ cat 1.txt
this \
is \
a \
long \
line
and another \
line
sed -e ':x /\\$/ {N; s/\\\n//g; bx}' 1.txt
this is a long line
and another line
(译者:该脚本有一个带地址的命令组,是否匹配这个地址正则表达式作为是否执行命令组的条件。如果当前输入行不是以“\”结尾,则匹配地址失败,跳过命令组,打印模式空间的内容,开启新一轮循环;否则,执行命令组,N命令会追加读入的下一行,s命令把反斜杠和换行符“\\n”替换为空,注意表达式内有三个反斜杠,前两个“\\”转义成一个字面上的“\”,然后无条件跳转到开始位置,没有开启新的循环情况下,继续前面的操作。)
#备忘录:上面的程序要求GNU sed。非GNU sed需要在“:”和“b”后面换行。
合并空白字符开头的行示例(例如,SMTP头):
$ cat 2.txt
Subject: Hello
World
Content-Type: multipart/alternative;
boundary=94eb2c190cc6370f06054535da6a
Date: Tue, 3 Jan 2017 19:41:16 +0000 (GMT)
Authentication-Results: mx.gnu.org;
dkim=pass header.i=@gnu.org;
spf=pass
Message-ID: <abcdef@gnu.org>
From: John Doe <jdoe@gnu.org>
To: Jane Smith jsmith@gnu.org
$ sed -E ':a; $!N; s/\n\s+/ /; ta; P; D' 2.txt
Subject: Hello world
Content-Type: multipart/alternative; boundary= 94eb2c190cc6370f06- 054535da6a
Date: Tue, 3 Jan 2017 19:41:16 +0000 (GMT)
Authentication-Results: mx.gnu.org; dkim=pass header.i=@gnu.org; spf=pass
Message-ID: <abcdef@gnu.org>
From: John Doe <jdoe@gnu.org>
To: Jane Smith jsmith@gnu.org
(译者:选项“-E”表示启用扩展正则表达式。先读入第1行,行号1不匹配地址最后一行$,执行N命令,读入第2行,形成“Subject: Hello\n World”,如果匹配一个换行符后跟一个或多个空白字符,s命令则把匹配部分替换成一个空格成“Subject: Hello World”,此次替换成功,流程跳转到开头处,此时行号是2,不匹配$,N读入第3行,形成“Subject: Hello World\nContent-Type: multipart/alternative;”,s命令匹配替换失败,不执行t分支命令,那么,P命令打印模式空间内容至第一个换行符(含),即“Subject: Hello World”,D命令把P命令打印过的内容删除,此时空间中遗留有“Content-Type: multipart/alternative;”,开始第二个循环……。)
#可移植性(非GNU sed)变体是:
#sed -e :a -e ‘$!N; s/\n */ /; ta’ -e ‘P; D’ 2.txt
(译者:经过测试这个功能不一样,“*”代表0个或多个,这样把所有的换行符都替换掉了。下面这个可以。
sed -e :a -e '$!N; s/\n \+/ /; ta' -e 'P; D' 2.txt
)
7.2 行文本居中
该脚本使一个文件中的所有行文本在一个80列宽的行中居中。为了改变那个列宽,必须在“{ . . . }”代码中实现80个空格。
注意,如何使用缓冲区命令来分离正则表达式中的匹配部分,这是一种常见的技术。
#!/usr/bin/sed -f
# center.sed
# 在模式空间缓冲区中加入80个空格
1{ # 只有在读入第一输入行时才执行以下命令组
x # 交换两个空间的内容,把第一行文本保存到保持空间中
# 把保持空间(里面是空的)转到模式空间中,起到清空作用
s/^$/ / # 匹配模式空间中的空行并替换成十个空格
s/^.*$/&&&&&&&&/ # &引用匹配到的十个空格,八个&替换后形成80个空格
x # 交换两个空间的内容,80个空格转移到保持空间中
# 第一行的文本又转回到模式空间中
}
# 删除空间当前行的前导和尾随空白(包括空格和制表符)
# 原文使用“y/tab/ /”,经过较长时间的摸索,终于明白,tab是指制表符!
# 而y命令不允许使用正则表达式(\t),如果用按tab键代替,
# 则会提示源和目标字符长度不一致,所以
# y/tab/ / # 原文的编码,测试中提示有错误
# 为何不使用s命令
s/\t\+/ / # 把一个或多个制表符替换成空格
s/^ *// # 替换前导空格,注意表达式中有个空格
s/ *$// # 替换尾随空格
# 模式空间中先追加一个换行符,再追加保持空间的80个空格的备份
G
# 模式空间中前81个字符保留下来,这是通过圆括号分组内容被“\1”反向引用替换实现的
# 为什么是81个字符而不是80个字符,是前面的G命令在行中加了一个换行符
s/^\(.\{81\}\).*$/\1/
# s命令搜索模式部分中的前半部分“^\(.*\)\n”表示要匹配从头开始到换行符为止,
# 圆括号内的子表达式会匹配模式空间中的当前行被前面处理过的文本,
# 在替换部分用“\1”反向引用这部分。
# 后半部分"\(.*\)\2"表达式设计得非常巧妙,“\2”反向引用第二个子表达式内容,
# 说明这两者“\(.*\)\2”完全相同。而此时模式空间中字符串,在换行符后只有空格,
# 而空格数量视“\1"内容的长度而定,此消彼长,
# 所以,“\2”引用的内容只能是模式空间中字符串的后面一半空格。
# 替换部分“\2\1",巧妙地把一半的空格放在处理过的当前行文本前面,
# 从而实现了行文本居中功能。
s/^\(.*\)\n\(.*\)\2/\2\1/
$ cat 2.txt
Subject: Hello
world
Content-Type: multipart/alternative;
boundary=94eb2c190cc6370f06054535da6a
Date: Tue, 3 Jan 2017 19:41:16 +0000 (GMT)
Authentication-Results: mx.gnu.org;
dkim=pass header.i=@gnu.org;
spf=pass
Message-ID: <abcdef@gnu.org>
From: John Doe <jdoe@gnu.org>
To: Jane Smith <jsmith@gnu.org>
$ chmod +x center.sed
$ ./center.sed 2.txt
Subject: Hello
world
Content-Type: multipart/alternative;
boundary=94eb2c190cc6370f06054535da6a
Date: Tue, 3 Jan 2017 19:41:16 +0000 (GMT)
Authentication-Results: mx.gnu.org;
dkim=pass header.i=@gnu.org;
spf=pass
Message-ID: <abcdef@gnu.org>
From: John Doe <jdoe@gnu.org>
To: Jane Smith <jsmith@gnu.org>
7.3 数递增
这个脚本是演示如何在sed中进行算术计算的少数脚本之一。这确实是可行的,但必须手动完成。(sed大师Greg Ubber编写了一个dc RPN的实现,它与sed一起发布。)
要实现一个输入的整数加1,您只需在该数的个位数上加1,然后用相加后得到的数字替换原来的数字,例如个位数是2,增加1后变成3,用3替换原来的2。当然有一个例外,当被加数是9时,加1后,该位数是0,它前面的位要加1,这样往前推进,一直到前位的数不是9为止。例如数字1299999,加1后,变成1300000。
Bruno Haible提供的解决方案是非常智能的,因为它使用了一个缓冲区;如果没有限制缓冲区的数量,那么,可采用7.10节的更快算法[cat -n]。它的工作原理是用下划线替换尾随的一个或多个9,然后使用十个s命令递增最后出现一位数字,然后再用0替换所有的下划线。
#!/usr/bin/sed -f
# increment.sed
# 包含有非数字的输入行,予以删除,立即开始下一轮循环
/[^0-9]/ d
# 用下划线“_”替换所有尾随的连续的9。
#(当然可以不用下划线,使用其他的任意非数字的单字符也行)
# 这里使用了一个条件分支t命令,如果s命令替换成功,则,
# 流程跳转到标签d处,不断循环,直到不是9为止。
# 例如,299 29_ 2__。
:d
s/9\(_*\)$/_\1/
td
# 只递增最后的数字。如果模式空间中必须要新增一个数字,
# 则下面第一个s命令将新增一个最有效的数字1。
# 下面的十个s命令类似于多条件选择语句——其他语言的“switch/case”,
# 其中的任何一个正则表达式匹配且替换成功后,就跳转到标签n处。
# 否则,流程继续执行下一条语句。例如:1_ --> 2_;2__ --> 3__。
s/^\(_*\)$/1\1/; tn # 匹配到空行、0个或多个“_”,则在行前加一个1
s/8\(_*\)$/9\1/; tn # 在行尾部匹配到8后跟的0个或多个“_”,则这个8替换成9
s/7\(_*\)$/8\1/; tn
s/6\(_*\)$/7\1/; tn
s/5\(_*\)$/6\1/; tn
s/4\(_*\)$/5\1/; tn
s/3\(_*\)$/4\1/; tn
s/2\(_*\)$/3\1/; tn
s/1\(_*\)$/2\1/; tn
s/0\(_*\)$/1\1/; tn
:n
# 利用y命令,把所有的下划线转换成0.
y/_/0/
$ chmod +x increment.sed # 赋予执行权
$ echo 8 | ./increment.sed
9
$ echo 8299 | ./increment.sed
8300
[GNU sed 4.5 版参考文档全文翻译 各命令和随带20个示例详细解析(八)] (https://blog.csdn.net/qq_39785418/article/details/90084594)
如果您觉得译文对您有帮助,不妨给个 微信打赏 翻译不易,各位的支持,能激发和鼓励我更大的热情。谢谢!