免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1362 | 回复: 0

sed基础概念 [复制链接]

论坛徽章:
0
发表于 2007-11-19 20:57 |显示全部楼层

                这是一篇从CU SHELL版移过来的文章
这篇文章是我学习sed后的一点感受,我的实际工作用不上sed,学习它仅仅是不想让花了我42RMB的《sed&awk》一直躺在书架上。我对
本版的帖子做过一次过滤,回复较多的帖子大概地浏览了一遍,搜集例子中的数据做练习。sed&awk讲了一些sed的基础概念,我感受最深的就是
行的概念了,所以我想说说我的一点理解,显然浅显,但正适合初学者(如我)阅读。
环境{
        GNU/Linux
        bash 3.0
        GNU sed 4.1.4
}
先看两个例子:
对于如下sed命令,大家都知道d删除的不是字符串“pattern”,而是包含字符串pattern的一整行
        sed '/pattern/d' testfile
所以d命令的操作是基于行的。类似的,p命令也是基于行的,有这样一个例子
echo "ABA,aaa" | sed -n '/^[A-Z]*[,][a-z]*/p'
echo "ABA,aaa" | sed -n '/^[A-Z]*[^,][a-z]*/p'
两个命令的屏幕输出完全一样,令人迷惑为什么“看起来相反”的正则表达式匹配的结果却一样,其实"显示结果一样!=正则表达式匹配结果相同",换成如下命令试一试
echo "ABA,aaa" | grep --color '^[A-Z]*[,][a-z]*'
echo "ABA,aaa" | grep --color '^[A-Z]*[^,][a-z]*'
结果很显然,sed中p命令是基于行的,把包含匹配的整个行输出到屏幕。
(包括下面的每个例子,请动手实际操作一下,观察一下屏幕输出与你的预想结果是否一致)
sed有一套编辑命令,都是基于行操作的,虽然功能不同,但是你用基本的sed概念去理解这些命令,将很快掌握sed。本文先介绍两个sed的基础概念,然后用这个基础概念结合例子理解sed常用的编辑命令。
1.请永远记住这个规则:
[color="Red"]一次读取一行
sed把输入看成文本流。将一个文本文件当成一串以'\n'分隔的有限长的字串。sed每次读入一行(为当前行),将其保存在内存缓冲区(称做模式空间),并将脚本中所有操作命令依次应用于当前行。(如果你读过过UNIX入门一类的书籍,对文本流这个概念一定不会陌生,发挥你的想像,想像一下sed将一行一行的文本,看成是相连的串,用流来形容,非常贴切吧)
截取Xorg.conf中的一段文本做例子,体会一下“一次读取一行”是怎么回事:
Section "InputDevice"
        Identifier  "Keyboard0"
        Driver      "kbd"
        Option                "XkbModel"        "pc105"
        Option                "XkbLayout"        "en_US,ru"
EndSection
#==================================================
Section "InputDevice"
        Identifier  "Mouse0"
        Driver      "mouse"
        Option            "Protocol" "auto"
        Option            "Device" "/dev/mouse"
        Option            "ZAxisMapping" "4 5"
#        Option            "Buttons" "5"
EndSection
执行下面这个sed命令之后,Section和EndSection之间的行将被删除,仅保留了中间一行
sed '/^Section/,/EndSection/d' testfile
尝试把文本中最后一个EndSection删除,再次执行上面这个命令,跟你预想的结果一样吗?修改后的文本不匹配/^Section/,/^EndSection/,为什么还会将其删除?
在这个例子中,sed并不把/^Section/,/^EndSection/之间的多个行一次读入到内存中,而仅仅是一次读一行,将其复制到内存中的某
个缓冲区,sed将这个缓冲区叫做模式空间,模式空间存放的内容叫做当前行,然后在这个当前行中搜索开始字符串Section,找到后执行d删除命令,继
续读取下一行,搜索结束字符串,如果没有找到,sed会认为当前行是Section和EndSection之间的行将其删除,而不管后面的行中是否包含
EndSection。如果后面没有结束字符串,一直删除到文件结束。
sed一次仅读取一行,不可能预知后面是否含有结束字符串,对于处理这样的例子,sed有一定的局限性。但是,一次处理一行的设计使sed简洁而高效。
SHELL版常有处理大文件(几百M甚至上G)时的效率问题,我对自造的一个不断重复内容的100M测试文件进行替换测试,sed虽然比不上awk,但速度还是可以接受的。有兴趣可以自己试一下:
for ((i=0; i> /tmp/boot.log
done && \
time sed -n 's/kernel/KERNEL/g'
在我的机器上{lenovo3110:p41.7G}测试结果如下(我的样本文件中7500行的boot.log有896行含有kernel,重复200次内容后文件是112MB):
time sed -n 's/kernel/KERNEL/g'
real 0m3.292s
time sed -i 's/kernel/KERNEL/g'
real 0m16.741s
虽然仅测试一个替换,不是测试一个脚本,但从中或多或少可以感受一下sed的简洁快速。
2. 规则1的附加规则:
        sed对读取的当前行依次运行整个脚本命令
        脚本最后一个命令执行完后sed输出模式空间的内容,并自动读取下一行再次从头执行脚本,如此不断循环
这是一个简单的脚本:
:labp
假设有一个文本内容如下:
     1        channel list:
     2        http://www.sopcast.com/chlist.xml
     3        A simple example of sp-sc command line.
     4        ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
     5        Start to transfer channel 6098, 8908 with VLC or mplayer
     6        by open the url: http://localhost:8908/tv.asf
对这个文本执行命令:
     sed -f script file
我们来看一下sed是如何执行的
sed读取第一行到模式空间,依次执行脚本中的所有命令,p命令将当前模式空间的内容输出到屏幕,然后冒号定义一个标签(这个标签后面没有用到,只是帮助理解脚本而设),之后由于执行完了所有的命令,sed默认会输出当前模式空间的内容。于是在屏幕上你将看到两行
     1        channel list:
     1        channel list:
执行完了脚本中所有的命令后,sed会自动读取一下行,再次循环整个脚本。如此不断循环,直到处理完文件的最后一行后结束退出sed。
猜测一下用下面的脚本处理后面的文本,结果会怎样?
s/Unix/UNIX/g
s/UNIX/UNIX System/g
对下面的文本执行命令:        sed -f script file
A
beginners guide to the Unix and Linux operating system. Eight simple
tutorials which cover the basics of UNIX / Linux commands.
对输出结果自已解释一下吧。
3.
[color="Red"]n,d,N,t,T,b命令以不同的方式影响文本流
如果没有if,for,while,可以想像一下C语言能够做什么?总是让脚本顺序执行,有很大一部分问题将不能解决,必须用某些命令控制文本流。
用上面的两个基础概念来理解这几个命令
[color="Red"]1) n
cat -n /etc/profile > /tmp/file
sed -n 'p;n' /tmp/file
sed -n 'n;p' /tmp/file
这两个例子非常奇妙地利用n命令打印奇数行和偶数行,简单地分析一下:
p
n
sed
首先读取输入文件file中的第一行复制到模式空间,对模式空间的当前行依次运行脚本中的每一个命令,p命令将当前行输出到屏幕,然后n命令迫使sed读
取下一行(第二行),之后由于脚本所有的命令已经执行完毕,sed会将当前模式空间的内容(此时即第二行)输出到屏幕(-n抑制了这个操作),接着读取第
三行到模式空间,并自顶向下运行脚本命令,开始第二次循环。
在这里要注意两次读取操作:
1. n命令迫使sed读取下一行到模式空间;
2. 脚本中所有的命令执行完后{p;n},sed会自动读取下一行,并返回脚本顶端开始下一次循环。
跳过一些不想在其上执行命令的行,这是n命令的拿手好戏。下面例子对/var/log/xorg.0.log执行如下脚本,将对包含"(II)"的行不执行替换操作:
/(II)/{
        n
}
s/SIS/sis/g
[color="Red"]2) d
d命令也会迫使sed读取下一行,不过与n命令有一个微妙的区别。
d命令删除模式空间的内容,命令执行后模式空间没有内容,导致d命令后面的其它命令将不能作用于模式空间,迫使sed读取下一行,并返回脚本顶部开始下一次循环。
我碰到过这样一个问题:写了一个1000行的C源程序,其中用到一个变量getnum,想将其替换为get_int_num,但是函数isyn(){...}内的getnum保持不变。
我第一次想用n命令实现:
/isyn()/,/^}/{
        n
}
s/getnum/get_int_num/g
结果失败了,具体的例子用一个简短的数据来说明一下:
将下面这个六行数据中除A~S的行外,所有的小写字母替换为*号
     1        channel list:
     2        http://www.sopcast.com/chlist.xml
     3        A simple example of sp-sc command line.
     4        ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
     5        Start to transfer channel 6098, 8908 with VLC or mplayer
     6        by open the url: http://localhost:8908/tv.asf
即变成:
     1        ******* ****:
     2        ****://***.*******.***/******.***
     3        A simple example of sp-sc command line.
     4        ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
     5        Start to transfer channel 6098, 8908 with VLC or mplayer
     6        ** **** *** ***: ****://*********:8908/**.***
脚本如下:
/A/,/S/{
        n
}
s/[a-z]/*/g
当sed读取第三行到模式空间为当前行时,脚本搜索到了A并执行n命令读取第四行,此时sed并没有返回脚本顶部重新执行命令,而是继续执行n命令后的s命令,将替换操作作用于第四行。
当我发现第四行作了替换后,尝试多次使用n命令失败,最后用p命令和d命令组合,变相地跳过了A~S行
/A/,/S/{
        p
        d
}
s/[a-z]/*/g
sed
读取第三行到模式空间后,先输出到屏幕后删除,根据d命令的特性,被d删除的模式空间没有内容,将不能对它进行脚本后s/[a-z]/*/g的替换操作,
迫使sed读取第四行并返回脚本顶部开始执行,由于第四仍包含在/A/,/S/内,将再次执行{p;d}操作,如此循环将/A/,/S/内的行变相地跳过
而不作替换。
[color="Red"]3) N
N命令与n命令的区别是
n读取下一行,并用读取的行将当前模式空间的内容“挤”出去,而N命令读取下一行后将读取的行追加到模式空间,两个行之间用一个'\n'分隔。这有点类似于>和>>重定向符
假如文件当作一个模式空间的话,执行下面的命令n次
        echo "Hello,World." > file
文件内将只有最后一次写入到其中的Hello,World.而如果执行的是下面的命令
        echo "Hello,World." >> file
文件内将有n行Hello,World。
回到那个输出文件奇数/偶数行的例子,可以用下面的命令输出奇数/偶数行:
(大写的P命令输出模式空间第一个'\n'前的内容)
    sed -n 'N;P' file
    sed -n 'P;N' file
如果要输出偶数行
   
sed -n '1!P;1!N' file
这个例子很好地说明了N命令是如何执行的,并再一次证明了这个附加规则“脚本最后一个命令执行完后sed输出模式空间的内容,并自动读取下一行再次从头执行脚本,如此不断循环”
[color="Red"]4) t,T,b (下面仅举几个例子,解释起来太费劲了)
这三个都是跳转命令。t和T是对s替换操作是否成功进行测试,然后决定如何跳转,类似if语句;而b命令则无条件地跳转。
返回上面的一个例子:
/A/,/S/{
        p
        d
}
s/[a-z]/*/g
可以将这个脚本修改为(不带标签的b命令将跳转到脚本的底部):
/A/,/S/{
        b
}
s/[a-z]/*/g
请用上面的两个基础概念自行解释一下运行这个脚本的详细过程
下面这个例子试图将/A/,/S/之间的多个行合并为一行,但是没有成功,你可以用前面的文本测试一下:
/A/,/S/{
        :lab
        $!N
        s/\n//g
        t lab
}
可以将其修改为这样
/A/{
        :lab
        /S/b
        $!N
        s/\n//g
        t lab
}
最后,用这个脚本可以删除下面的变态C注释
/\/\*/{
        /\*\//s@/\*.*\*/@@        #删除在同一行上的 /* ...... */
        t                        #如果替换成功则转到脚本底部,结束一次/*.....*/替换
        s@/\*.*$@@                #不成功则继续执行:删除/*到行尾(/*前面可能有代码)
        :lab
        n
        s@.*\*/@@                #搜索是否包含结束串*/将其替换
        t                        #如果替换成功说明已经删除了/*...*/
        s@^.*$@@                #不成功则将行替换为空:这里不能用d,d会跳出这个循环
        b lab
}
测试用的C文本
#include
main()
{
        /*需要整行删除*/
        printf("本句和注释之间的空格不要去掉\n");         /*just a test
        ******需要整行删除
        /////需要整行删除
        需要整行删除,后面空行不要删除*/
        printf("汉字不能被删除\n");
/*test  注意前面空行不能删除
*/         printf("test:前面的空格不要去掉\n");/*a
*/printf("test3\n");
        return(0);
}
不够完美,须要执行两次sed:
        sed -f script test.c | sed -f script
不当之处,尚请批评指正。感谢CU SHELL版的各位xdjm,让我在这里学会了很多东西。
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/15571/showart_426229.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP