sed 笔记

sed 简介

sed(stream editor)是一个流编辑器,一次处理流的一行内容。

sed 命令模式
sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)

常用options

  • -n 取消默认输出,只有经过sed特殊处理行(或者动作)才会被列出来。
  • -e 多重编辑模式。在命令行中同时指定多个操作指令时才需要用到。
  • -f 指定sed脚本文件名。
  • -i 直接修改文件内容。

sed 不会直接修改原文件,要直接修改原文件有2种方式:
1. 使用 -i 选项;
2. 使用重定向,重定向的目标文件不能与输入文件相同。

sed 的处理流程

每条操作指令由pattern和procedure两部分组成,pattern一般是用 ‘/’ 分隔的正则表达式(在sed中也可能是行号),procedure则是一连串的编辑命令。

sed的处理流程简化为:
1. 读入新的一行内容到缓存空间;
2. 从指定的操作命令中取出第一条指令,判断是否匹配pattern;
3. 如果不匹配,则忽略后续的编辑命令,回到第2步继续取出下一条指令;
4. 如果匹配,则针对缓存的行执行后续的编辑命令;完成后,回到第2步继续取出下一条指令;
5. 当所有指令都应用之后,输出缓存行的内容;回到第1步继续读入下一行内容;
6. 当所有行都处理完成之后,结束。

从流程上可以看出,前一个命令的执行结果影响后一个命令的执行,命令按先后顺序执行。

地址

地址是用来确定希望编辑的行,可以用数字、正则、美元符($)来表示。也可以用逗号分隔的两个地址来表示希望编辑的范围,这个范围包括这两个地址所在的行。

[起始地址, 结束地址]

举例:


3              // 表示希望编辑第3行。
$              // 表示希望编辑最后那行。
4, 10          // 表示希望编辑4至10行,[4-10]。
2, $           // 表示希望编辑2至最后行,[2-$]。
/href/         // 表示希望编辑含有 href 字符的那些行。
2, /href/      // 表示希望编辑从第2行开始直到并包括第一个含有 href 字符的行。
/meta/,/link/  // 表示希望编辑从第一个含有 meta 字符的行 到 第一个含有 link 字符的行,中间的行就算没有 meta 或 link 字符也会被命令处理。

正则元字符

因为sed用到很多正则,所以先补充点正则的知识。


^     表示行的开头。
$     表示行的结尾。
\<     表示词首,如 \     表示词尾,如 abc\>表示以abc为尾的单词。
.     句点表示任意单个字符
*     星号表示某个字符出现了0次或多次。
[]    字符集合。如[0-9]表示数字0-9中的任意一个,[a-zA-Z]表示字母中的任意一个。如果字符集以^开头表示非,如[^0-9]表示非数字。

这些正则跟其他编程语言中的正则差不多,除了 \<\>

sed 命令

有了前面的基础,就可以学习sed命令并实际运行了。

几乎所有命令都是这样:[address[,address]][!]{cmd} 。address可以是前面提到的定址中的任何一个。

s 替换命令

s 的命令模式是这样的:[address[, address]]s/pattern/replacement/g


sed "s/my/you/g"  file
把file里的 my 替换为 you

sed "3,$s/my/you/g"  file
把file里 第3行 到 最后行 的 my 替换为 you

sed "3,$s/t/T/3" file
把file里 第3行 到 最后行 的第三个 t 替换为 T

sed "3,$s/t/T/3g" file
把file里 第3行 到 最后行 的 第三个开始 的 t 替换为 T

sed "1,3s/^/head/g" file
把file里 第1行 到 第3行 的行首加上 head

sed "1,3s/¥/tail/g" file
把file里 第1行 到 第3行 的行首加上 tail

sed "s/<[^>]*>//g"  file
去除file的html标签,标签通过正则 <[^>]*> 匹配,替换为空也就是删除了。

a 命令和 i 命令

a命令是append,i命令是insert,都可以用来添加行。区别在a是在匹配行后面添加新行,而i是在匹配行的前面添加新行。
命令模式: sed "[address[,address]]i newline" filesed "[address[,address]]a newline" file, i命令后面可以有任意数量的空格符。

举例:


sed "/coderbee/a    add new line ." csv.txt
在每个包含 coderbee 的行的后面插入 newline 的新行。

sed "1,/coderbee/i    add new line ." csv.txt
从第1行直到第1个包含 coderbee 的行,在这些行的前面插入 newline 的新行。

c 替换匹配行命令

sed "2c newline" csv.txt
把第2行替换为 newline

sed "/coderbee/c newline" csv.txt
把第匹配coderbee的行替换为 newline

d 删除匹配行命令

其实d命令是删除模式空间的所有行。

sed "2d" csv.txt
把第2行删除

sed "/coderbee/d" csv.txt
把第匹配coderbee的行删除

p 打印匹配行命令

sed -n "2p" csv.txt
打印第2行

sed -n "/coderbee/p" csv.txt
打印匹配coderbee的行

D 命令

删除模式空间中第一行的内容,d命令是删除模式空间的所有内容。D不会导致读入新行,它会返回到最初的编辑命令,重新应用在模式空间剩余的内容上。

删除连续的空行: sed "/^$/{N; /^$/D}" file,(测试没效果)

p/P 打印命令

p打印模式空间的所有内容,而P只打印模式空间的第一行内容。一般情况下,p/P命令是和 -n 选项一起使用。

y 转换命令

命令模式:[address[,address]]y/SET1/SET2/ ,y 转换命令相当于定义了一个映射表,把集合SET1中对应的字符转换为集合SET2中对应的字符,两个集合的长度必须一致。

sed -n "1,3y/abc/ABC/" file
上面的命令对1-3行进行这样的转换: a-A, b-B, c-C。

n 取下一行命令

n命令将下一行的内容提前读入,并将模式空间中的行(之前读入的行,可能是被前面的命令处理过后的结果)输出到屏幕(默认输出),然后后续的命令会应用到新读入的行上。因此n命令会改变sed的控制流程。

echo -e "abc\nxyz\n123\n987" | sed "/xyz/{n;d}"
结果为:


abc
xyz
987

sed 的输入有4行,匹配到xyz时,执行n命令(把xyz输出到屏幕,然后读入xyz的下一行123),执行d命令,删除了123,所以输出里没有123。

N 命令

N命令将下一行的内容读取到当前模式空间,但是与n命令不一样的地方是N命令并没有直接输出当前模式空间中的行,而是把下一行追加到当前模式空间,两行之间用回车符\n连接。两行合并成1行后,元字符^变为匹配模式空间的开始,$变为匹配模式空间的结尾,相当于只有一行,忽略了中间的回车符。

echo -e "1\n2\n3\n4" | sed "/2/{N; d}"
得到的结果只有14两行。

r/w 读写文件命令

但定位匹配行时,r命令可以读取指定的文件,并把文件的内容输出到输出流中,w命令可以把结果输出到指定的文件。r/w命令会把空格后的内容当作文件名,如果r/w命令后面还有命令,应该用-e选项开启一个命令。

sed -n "1r tmp" csv.txt
用 -n 属性屏蔽掉默认输出,当匹配到第1行时,读取文件tmp的内容到输出流。

sed -n "3w tmp" csv.txt
用 -n 属性屏蔽掉默认输出,当匹配到第3行时,输出文件tmp的内容。

现在有个文件 read.file,内容为:


content from file
content from file, line2

执行命令: echo -e "abc\nxyz\n123\n987" | sed -e "/xyz/r read.file" -e "s/xyz/XYZ007/g;" -e "y/027/+-*/" ,得到结果:


abc
XYZ++*
content from file
content from file, line2
1-3
98*

从输出的结果可以看到,r命令是在r命令之后的所有命令执行完后才执行的,与一般情况下sed命令按顺序执行还是有所不同的。

保留空间

保留空间(Hold Space)用于保存模式空间的内容,内容可以在模式空间与保留空间之间复制,除了下面的命令,其他命令无法匹配也不能修改保留空间的内容。

操作保留空间的命令:


名称                         命令               说明
保存(Hold)             h/H            将模式空间的内容复制或者追加到保留空间
取回(Get)              g/G            将保留空间的内容复制或追加到模式空间
交换(Exchange)      x               交换保留空间与模式空间的内容

对于保存和取回,小写命令会覆盖目的空间的内容,大写是将内容追加到目的空间,追加的内容和原有的内容用 \n 分隔。

多个匹配

sed支持匹配多个模式,可以用 -e 选项开启多个匹配。

举例,把file里 [1-3] 行的 my 替换为 you, [3-5]行的 t 替换为 T :
sed "1,3s/my/you/g; 3,5s/t/T/g" file
这个也等价于:
sed -e "1,3s/my/you/g" -e "3,5s/t/T/g" file

圆括号匹配与变量

圆括号括起来的正则表达式所匹配的字符串可以当成变量来使用,按定义的顺序依次为 \1, \2, … 。
& 可以当作被匹配的变量,这样可以在被匹配的变量左右加点东西。
通过变量还可以用来从流中抽取特定的值。


echo "abc=123" | sed  "s/\([^=]*\)=\(.*\)/\1==\2/g"
将得到  abc==123

echo "123456789"  | sed "s/5/*&*/g"
将得到  1234*5*6789

echo "url='www.google.com'" | sed "s/url='\([^']*\)'/\1/g"
将得到  www.google.com

命令打包

命令可以有多个,用逗号分隔,按先后顺序执行,用大括号括起来作为嵌套命令。


echo "abc1231" | sed -n -e "s/a/A/g; s/1/++/g; p"
用逗号分隔多个命令,将得到 Abc++23++

echo -e "abc 1\n    abc xyz" | sed -n "/abc/{/xyz/p}"
嵌套命令,先匹配abc,再匹配xyz,成功后打印,得到:   abc xyz

echo -e "abc 1\n    abc xyz" | sed -n "/abc/{/xyz/{s/^ *//g;  p}}"`
嵌套命令,先匹配abc,再匹配xyz,成功后,删除行首的空格,最后打印,得到:abc xyz

一个真实案例

最近碰到的一个任务是这样的:有一堆web页面及其引用的js、css、图片,现有这堆东西只有有限的页面还在使用,为了减小软件大小及以后维护方便,需要把不再使用的资源删除,只保留必须的。

我的做法是这样的:以html文件为入口,解析里面引用的js、css,然后再解析css,得到里面引用的图片,解析的结果是引用的url,通过管道把url传给shell脚本,由shell脚本进行建立目录和拷贝文件。
当然,这个依赖于美工良好的编写风格,不在html里引用图片啥的,html的标签都写成一行等等。如果编写没有一致的风格就增加解析难度了。

html里主要匹配下面这样的标签:
<script charset="gb2312" src="path/to/js" type="text/javascript"></script>
<link href="path/to/css" >

css里主要匹配下面这样的:
....background:url('url/to/image');

下面是我的代码(其实是写成一行的):


cat *.html
     | sed -n -e "//\1/g;p};  /.*/\1/g; p}"
     | sed "{s/^[ \t]*//g}"
     | sort | uniq
     | cp.sh

cat ./css/*.css
     | sed -n -e "/.*url([^)]*).*/{s/.*(\([^)]*\)).*/\1/g; p}"
     | sort | uniq
     | cp.sh

shell脚本cp.sh主要建立目录,并执行拷贝:


dstDir=/d/clearDir/UI/

read url

while [ ! -z "$url" ]
do
     dir=`dirname $url`
     #[ ! -z $dir ] mkdir -p $dstDir$dir
     mkdir -p $dstDir$dir
     cp $url $dstDir$url
     read url
done

参考资料


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据