注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Sed 介绍和教程[4]  

2011-01-15 15:18:05|  分类: shell |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

持有缓存

    到现在为止,我们讨论了sed的三个概念:(1)输入流或是修改前的数据,(2)输出流或是修改后的数据,(3)模式空间,或是包含可被修改和可被发送到输出流的缓存。< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

    但还有一个概念没有讲:持有缓存或保持空间,可以把它想成是一个空闲的模式缓存,它可以用来在模式空间复制或是记录数据为以后使用。有五个命令可以使用这个持有缓存。

”x”交换

“x”命令用来交换(eXchange)模式空间和持有缓存,”x”命令单独使用没什么用,执行sed命令:

sed 'x'

    它会在开始添加一个空白行,并删除最后一行,看起来它没有怎么改变输入流,但实际上sed命令改动了每一行。

    持有缓存开始包含一个空白行,当”x”命令修改第一行,第一行被保存到了持有缓存中,且空白行占有了第一行的位置,第二个”x”命令交换第二行与持有缓存,这时持有缓存中是第一行内容。以此类推,每一行都与前一行交换。最后一行被放入保持空间,但是它没有机会交换,所以它在程序退出时还在持有缓存中,没有机会被打印。这表明了使用持有缓存时一定要小心,因为除非你指明要打印,否则它是不会输出的。

上下文grep的例子

    持有缓存的其中一个作用是记录以前的行,这个作用的一个例子就是像grep一样,显示匹配一个模式的所有行,并且它可以显示这个模式的前一行和后一行,即如果第8行包含这个模式,这个例子可以打印第789行。

    实现这一功能的其中一种方法是看一行中是否有这个模式,如果它没有包含这个模式,就将当前行放入持有缓存,如果有,就将持有缓存中的行打印,然后打印当前行,然后打印下一行。在每三行组成的组之间,打印连字符,脚本检查一个参数是否存大,如果不存在,打印错误。通过关闭单引号的机制将一个参数传入sed脚本,将”$< xmlnamespace prefix ="st1" ns ="urn:schemas-microsoft-com:office:smarttags" />1”插入脚本,再用单引号开始:

#!/bin/sh

# grep3 - prints out three lines around pattern

# if there is only one argument, exit

 

case $# in

    1);;

    *) echo "Usage: $0 pattern";exit;;

esac;

# I hope the argument doesn't contain a /

# if it does, sed will complain

 

# use sed -n to disable printing

# unless we ask for it

sed -n '

'/$1/' !{

    #no match - put the current line in the hold buffer

    x

    # delete the old one, which is

    # now in the pattern buffer

    d

}

'/$1/' {

    # a match - get last line

    x

    # print it

    p

    # get the original line back

    x

    # print it

    p

    # get the next line

    n

    # print it

    p

    # now add three dashes as a marker

    a\

---

    # now put this line into the hold buffer

    x

}'

    你可以用这个来显示在一个关键词附近的三行:

grep3 vt100 </etc/termcap

hH持有

    “x”命令交换持有缓存和模式缓存,两个空间都发生改变,”h”命令将模式缓存拷贝到持有空间,而模式空间是不变的,下例使用持有命令完成上例的功能:

#!/bin/sh

# grep3 version b - another version using the hold commands

# if there is only one argument, exit

 

case $# in

    1);;

    *) echo "Usage: $0 pattern";exit;;

esac;

 

# again - I hope the argument doesn't contain a /

 

# use sed -n to disable printing

 

sed -n '

'/$1/' !{

    # put the non-matching line in the hold buffer

    h

}

'/$1/' {

    # found a line that matches

    # append it to the hold buffer

    H

    # the hold buffer contains 2 lines

    # get the next line

    n

    # and add it to the hold buffer

    H

    # now print it back to the pattern space

    x

    # and print it.

    p

    # add the three hyphens as a marker

    a\

---

}'

在持有buffer中保存多行

    “H”命令允许你将几行结合起来放入持有缓存中,它的表现像”N”命令一样,将多行追加到缓存,在行之间有”\n”。你可以在持有缓存中保存多行,并在找到特定模式之后,将它们输出。

    在下例中,一个文件用空格做为一行的第一个字符被视为是一个延续字符,文件/etc/termcap/etc/printcapmakefile和邮件信息使用空格或tab来表示一个条目的继续。如果你想打印一个单词前的条目,你可以用下面的脚本,我使用”^I”表示tag字符:

#!/bin/sh

# print previous entry

sed -n '

/^[ ^I]/!{

    # line does not start with a space or tab,

    # does it have the pattern we are interested in?

    '/$1/' {

       # yes it does. print three dashes

       i\

---

       # get hold buffer, save current line

       x

       # now print what was in the hold buffer

       p

       # get the original line back

       x

    }

    # store it in the hold buffer

    h

}

# what about lines that start

# with a space or tab?

/^[ ^I]/ {

    # append it to the hold buffer

    H

}'

    你还可以用”H”命令来扩展上下文grep,在这个例子中,程序会打印模式前的两行,而不是一行,限制两行的方法是用”s”命令来保持一个新行,并删除多余的行,我称它为grep4

#!/bin/sh

 

# grep4: prints out 4 lines around pattern

# if there is only one argument, exit

 

case $# in

    1);;

    *) echo "Usage: $0 pattern";exit;;

esac;

 

sed -n '

'/$1/' !{

    # does not match - add this line to the hold space

    H

    # bring it back into the pattern space

    x

    # Two lines would look like .*\n.*

    # Three lines look like .*\n.*\n.*

    # Delete extra lines - keep two

    s/^.*\n\(.*\n.*\)$/\1/

    # now put the two lines (at most) into

    # the hold buffer again

    x

}

'/$1/' {

    # matches - append the current line

    H

    # get the next line

    n

    # append that one also

    H

    # bring it back, but keep the current line in

    # the hold buffer. This is the line after the pattern,

    # and we want to place it in hold in case the next line

    # has the desired pattern

    x

    # print the 4 lines

    p

    # add the mark

    a\

---

}'

    你可以修改这个脚本,让它打印模式附近的任何行,如你所见,你必须记住什么是在持有空间,和什么是在模式空间。

使用gG获得

    除了交换持有缓存和模式空间,你可以用”g”命令将持有空间拷贝到模式空间,这会删除模式空间。如果你想追到模式空间,使用”G”命令。它会添加一个新行到模式空间,并将持有空间的内容拷贝到新行之后。

    下例是一个”grep3”的另一个版本,它的功能和前一个一样,但实现不一样,它说明了sed有多种方法来解决一个问题。重要的是你理解你的问题,并记录下你的解法:

#!/bin/sh

# grep3 version c: use 'G'  instead of H

 

# if there is only one argument, exit

 

case $# in

    1);;

    *) echo "Usage: $0 pattern";exit;;

esac;

 

# again - I hope the argument doesn't contain a /

 

sed -n '

'/$1/' !{

    # put the non-matching line in the hold buffer

    h

}

'/$1/' {

    # found a line that matches

    # add the next line to the pattern space

    N

    # exchange the previous line with the

    # 2 in pattern space

    x

    # now add the two lines back

    G

    # and print it.

    p

    # add the three hyphens as a marker

    a\

---

    # remove first 2 lines

    s/.*\n.*\n\(.*\)$/\1/

    # and place in the hold buffer for next time

    h

}'

    “G”命令使得一行有两份拷贝比较容易,假设你想将第一个16进制数变为大写,而不想用我之前告诉你的脚本:

#!/bin/sh

# change the first hex number to upper case format

# uses sed twice

# used as a filter

# convert2uc <in >out

sed '

s/ /\

/' | \

sed ' {

    y/abcdef/ABCDEF/

    N

    s/\n/ /

}'

    下面是只需要一次sed调用的方法:

#!/bin/sh

# convert2uc version b

# change the first hex number to upper case format

# uses sed once

# used as a filter

# convert2uc <in >out

sed '

{

    # remember the line

    h

    #change the current line to upper case

    y/abcdef/ABCDEF/

    # add the old line back

    G

    # Keep the first word of the first line,

    # and second word of the second line

    # with one humongeous regular expression

    s/^\([^ ]*\) .*\n[^ ]* \(.*\)/\1 \2/

}'

    Carl Henrik Lunde提供了一个更简单的方法:

#!/bin/sh

# convert2uc version b

# change the first hex number to upper case format

# uses sed once

# used as a filter

# convert2uc <in >out

sed '

{

    # remember the line

    h

    #change the current line to upper case

    y/abcdef/ABCDEF/

    # add the old line back

    G

    # Keep the first word of the first line,

    # and second word of the second line

    # with one humongeous regular expression

    s/ .* / / # delete all but the first and last word

}'

    这个例子只将字母”a””f”变为大写,我选这个例子是为了在显示方便,你可很容易地将所有字母变为大写。

流程控制

    如你所了解的一样,sed有它自己的编程语言。它是一个定制的简单语言。没有哪一个语言没有流程控制还能被称为完整。有三个sed命令可以做这件事,你可指定一个文本加一个冒号作为标签,标签后跟着命令,如果没有其它标签,分支会执行到脚本的最后。”t”命令用来测试条件,在我介绍”t”命令之前,我会给你看一个”b”命令的例子。

例子中记录段落,并且如果它包含某一模式(由参数指定),脚本就会被整段打印。

#!/bin/sh

sed -n '

# if an empty line, check the paragraph

/^$/ b para

# else add it to the hold buffer

H

# at end of file, check paragraph

$ b para

# now branch to end of script

b

# this is where a paragraph is checked for the pattern

:para

# return the entire paragraph

# into the pattern space

x

# look for the pattern, if there - print

/'$1'/ p

'

”t”测试

    你可以在一个模式找到后执行一个分支,你也许想在只有替换后执行一个分支,命令”t label”会在最后一个替换命令修改模式空间后执行分支。

    这个命令的作用之一就是递归模式,个旧市你想删除括号内的空格,括号也许是嵌套的,却也许你想删除像"( ( ( ())) )"这样的字符串,下例:

sed 's/([ ^I]*)/g'

只能移除最内层的空格,你也许需要管道四次来删除每层的空格,你可能会用正则表达式:

sed 's/([ ^I()]*)/g'

但这又会把括号集合删除。”t”命令可以来解决这个问题

#!/bin/sh

sed '

:again

    s/([ ^I]*)//g

    t again

'

添加注释的另一种方式

    如果你用的sed版本不支持注释,你可以用另一种方式来添加注释,用a命令对第0行操作:

#!/bin/sh

sed '

/begin/ {

0i\

    This is a comment\

    It can cover several lines\

    It will work with any version of sed

}'

有着糟糕文档的”;”

    还有一个没有在文档中很好解释的命令”;”,它可以用来接合几个sed命令到一行中,下面是我前面介绍过的grep4脚本,但没有注释和错误检查,它两个命令间有分号:

#!/bin/sh

sed -n '

'/$1/' !{;H;x;s/^.*\n\(.*\n.*\)$/\1/;x;}

'/$1/' {;H;n;H;x;p;a\

---

}'

    天啊,简直是一堆乱码。我想我已经说明了我的观点。就我感觉,分号唯一有用的时候是在命令行环境下输入sed脚本。如果你在一个脚本中写sed,就应让它可读性高一些,我已经说过很多sed版本除了第一行外,不支持注释。你可以在你的脚本里写上注释,在使用时删掉它们,这样做并不困难。毕竟,你现在也成为一个sed大师了嘛。我不会再告诉你如果写一个将注释删掉的脚本了,因为如果我告诉你,这简直就是对你智力的侮辱。另外,一些操作系统不允许你用分号。所以如果你看到一个有分号的脚本,它不会在非Linux系统下正常运行,就将分号换成换行符。(只要你不在csh/tcsh下用。但那是另一个话题了)

将正则表达式作为参数传递。

    在前面的脚本中,我提到过如果你在传递参数时有一个斜杠就会有问题,事实上,可能是正则表达式引起的麻烦。一下像下面一样的脚本可能会有时候不能正常执行:

#!/bin/sh

sed 's/'"$1"'//g'

    如果你传递的参数有”^.*[]^$”中的任何一个,你的脚本都会出错,如果有人在替换命令中使用了”/”,那么看起来就像是有四个分隔符,而不是三个。如果你参数中有”[”而没有”]”,会有语法错误提示。其中一个解决办法是在传递这种参数时在相应的字符前加上反斜杠。但是用户就必须知道哪个字符是特殊字符。

    另一种解决办法是在脚本中这些字符的每一个前加上反斜杠:

#!/bin/sh

arg=`echo "$1" | sed 's:[]\[\^\$\.\*\/]:\\\\&:g'`

sed 's/'"$arg"'//g'

    如果你想查找模式”^../”,脚本会在传递给sed之前将它转换成”\^\.\.\/”

命令总结

    如我前面所保证的一样,这里有一个不同命令的总结。第二列说明命令可不可以接受范围(2)或是一个位置(1)的限定。第四列说明命令会修改四个缓存中的哪个。一些命令会影响输出,另一些影响缓存。如果你记得模式空间是要输出的(除非使用了-n参数),这个表可以帮你了解这么多命令。

+---------------------------------------------------------+

 |Command   Address             Modifications to         |

 |          or Range      Input  Output Pattern   Hold   |

 |                       Stream Stream Space    Buffer |

 +--------------------------------------------------------+

 |=            -          -       Y         -      -       |

 |a            1          -       Y         -      -       |

 |b            2          -       -         -      -       |

 |c            2          -       Y         -      -       |

 |d            2          Y       -         Y      -       |

 |D            2          Y       -         Y      -      |

 |g            2          -       -         Y      -       |

 |G            2          -       -         Y      -       |

 |h            2          -       -         -      Y       |

 |H            2          -       -          -      Y       |

 |i            1          -       Y         -      -       |

 |l            1          -       Y         -      -       |

 |n            2          Y       *         -      -       |

 |N            2          Y       -         Y      -       |

 |p            2          -       Y         -      -       |

 |P            2          -       Y         -      -       |

 |q            1          -       -         -      -       |

 |r            1          -       Y         -      -       |

 |s            2          -       -         Y      -       |

 |t            2          -       -         -      -       |

 |w            2          -       Y         -       -       |

 |x            2          -       -         Y      Y       |

 |y            2          -       -         Y      -       |

 +--------------------------------------------------------+

结论

    我举例所用的脚本有可能会有更短的实现,但我选择这些脚本是为说明一些用法,所以我要使它清晰。希望你喜欢我的教程。

More References

This concludes my tutorial on sed. Other of my Unix shell tutorials can be found here. Other shell tutorials can be found at Heiner's SHELLdorado and Chris F. A. Johnson's Unix Shell Page
The Wikipedia Entry on SED
SED one-liners

  评论这张
 
阅读(1536)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017