sed学习笔记

sed介绍

sed是个流编辑器, 你可以想象成有一个管道, 文件从一端流进, 经过里面一系列加工后, 从另一端流出. 恩, 就这样理解算了. 这是一个很牛逼的工具, 作为Linux管理员, 最好熟悉下这个(还有awk, grep之类). 从一个简单例子开始:

echo 'hello, tony' | sed 's/tony/jack/'

输出: hello, tony. s替换的命令, /是分隔符. 就是把每中的tony换成jack. 当然, 你不一定要用/作为分隔符, 只要你愿意, 用什么都可以, 比如:

echo 'hello, tony' | sed 's:tony:jack:'

这里就用的:作的分隔符. 有一个要注意的是, sed默认替换行为是只影响到每一行中的第一个匹配项, 如果要影响所有的, 则要在后面加上g:

echo 'hello, tony! my name is tony too~' | sed 's/tony/jack/g'

输出: hello, jack! my name is jack too~

&可以引用被匹配的内容

有时候我们替换的内容是基于被匹配的内容上加工后形成的, 所以要替换的文本是不确定的. &可以引用前面匹配的内容:

echo 'hello, tony! my name is tony too~' | sed 's/tony/&2/g'

输出: hello, tony2! my name is tony2 too~. 注意上面把前面匹配的tony替换成了tony2.

sed和扩展正则表达式

我们知道在Linux系统中正则表达式有基本的和扩展的, 所谓扩展, 就是功能更丰富一些. 如下:

echo '123 abc' | sed -r 's/[1-9]+/& &/'

输出: 123 123 abc. 这里, 我们把一个数字组成的词复制一份. 在sed中, -r选项就是开启扩展正则功能, 因为+属于扩展正则的功能, 如果不开启的话, 会被解析成普通的字符.

利用分组(\1, \2, ... , \9)

我们知道在正则表达式里, 可以用()进行分组, 这在sed里同样可以利用. 例子:

 echo 'hello, tony jack' | sed -r 's/([a-z]+) ([a-z]+)/\2 \1/'

输出: hello, jack tony. 把tonyjack两个单词对换了. \1, \2, ... , \9 分别对应前面正则里的分组1, 分组2, ... , 分组9, 最多9个. 注意一点, 分组引用不只是只能出现在分隔符右边, 也可以出现在左边, 如:

echo 'hello, tony tony yes ok ok' | sed -r 's/([a-z]+) \1/\1/g'

输出: hello, tony yes ok. 这里的功能是去重.
在这前提到s命令后面加g, 会切换到全匹配模式, 即会扫描整行全部查找(默认只匹配第一个). 有时候我们的需求是确定匹配替换指定的第几个而不是全部, 可以在s的命令后面加上数字来指示. 例子:

echo 'hello world haha!' | sed 's/[a-z][a-z]*/T/2'

输出: hello T haha! 可以看到现在匹配替换的是第2个. 我们也可以结合g数字一起放到命令的后面组合使用, 表示从能匹配的第n个开始的后续所有匹配项. 举例:

echo 'hello world haha!' | sed 's/[a-z][a-z]*/T/2g'

输出: hello T T!. 可以看到, 现在是替换了从第2个开始的所有单词(由小写的a~z组成的单词).

/p 打印匹配行

sed默认行为是打印所有的行, 如果是s命令, 就用处理过的行替换处理前的行. 用g标识可以改变这一行为, 它会单独打印所匹配或者处理过的行. 例子:

printf 'my name is\nhaha, jack\n' | sed 's/my/your/p'
# 输出
# your name is
# your name is
# haha, jack

your name is这行输出了两次, 一次是sed命令自动输出, 另一次是标识p影响的作用. 如果我们现在只想打印匹配的行, 不要自动输出所有的行, 可以用-n选项, 例子:

printf 'my name is\nhaha, jack\n' | sed -n 's/my/your/p'
# 输出:
# your name is

可以看到, 现在只有能匹配的行才能输出了.

I标识

可以通过I标识来打开不区分大小写匹配. 例子:

echo 'hello, tony' | sed 's/Tony/jack/I'
# 输出:
# hello, jack

如果后面不加I标识, 则不会匹配成功.

一行执行多个命令

前面例子中每次只能执行一次s命令, 如果要处理多个命令怎么做呢, 当然我们可用管道把命令串起来, 就像:

echo 'hello, tony and jack' | sed 's/tony/Tony/' | sed 's/jack/Jack/'
# 输出:
# hello, Tony and Jack

但是在Linux中能一句话说清楚的就不绕个圏. 可以用-e选项来执行多个命令, 这样在sed处理每行时, 会依次执行. 例子:

echo 'hello, tony and jack' | sed -e 's/tony/Tony/' -e 's/jack/Jack/' 
# 输出:
# hello, Tony and Jack

选择要处理的行(addresses)

有时候我们我们只希望处理一些特定的行而并不是文件的所有行, sed当然支持这样的功能, 它通restriction command这样的语法实现. 其中restriction就是限制要处理的行. 比如下面一些场景:

  • 指定要处理的行数, 比如第8行
  • 指定某个范围内的所有行, 比如第1行到第8行
  • 包括某个模式匹配的所有行
  • 从文件的第一行到指定匹配某个模式的行
  • 从指定匹配的行到文件的最后一行
  • 从一个模式匹配到别一个模式匹配行当中的所有行

指定行数

这是最简单的指定限制, 通过指定行数来处理, 例子:

sed '1 s/hello/byby/' test.txt

s替换命令只会在第一行生效

正则模式匹配

只有那些符合指定正则模式匹配的行才会处理, 例子:

sed '/^[0-9][0-9]*/ s/hello/byby/' test.txt 

只处理那些开头是数字的行.

行数范围

我们可以处理指定从第几行到第几行之间的所有行处理, 例子:

# 处理第10行到第20行之间的数据
sed '10,20 s/hello/byby/' test.txt
# 处理第10到最后一行的所有数据
sed '10,$ s/hello/byby/' test.txt 

模式范围匹配

从匹配的第一个模式开始, 一直到第二个模式匹配结束.

sed '/start/,/end/ s/hello/byby/' test.txt 

start开始处理, 到end结束.

删除命令d

前面讲的s是替换命令, d是删除命令. 例子:

# 删除第2行到第三行之间的所有行
sed '2,10 d' test.txt

我们经常遇到一个场景是删除一个配置文件(properties)的所有注释, 可以这样写:

sed -e 's/#.*//' -e 's/[ ^I]//' -e '/^$/ d'  test.txt

上面这句由三个子命令组成, 它们的执行顺序也很重要. 分别来看一下. s/#.*//是把所有由#开头的内容去掉, 但是如果#前面有空格(space or tab), 就要做第二个命令处理: s/[ ^I]//, 就是把所有的空格去掉, 注意的是^I就是表示Tab. 最后一个命令就是所有空行(空行有两个来源, 一是文件本身的空行, 二是通过前面两条命令产生的空行)都通过命令d删除.

反转命令: !

!可以置反命令操作, 比如!d就是不删除的意思. 如下几个命令返回的结果是一样的, 都是只只输出第1到10行:

sed -n '1,10 p'
sed -n '11,$ !p'
sed '1,10 !d'
sed '11,$ d'

退出命令

遇到满足条件时退出流编辑操作, 如下:

sed '11 q' test.txt

只打印到第10行. 到第11行时就会退出. 因这退出操作的特殊性, 这个命令不支持范围匹配操作, 比如sed '2,3 q'就是错误的, 这个很好理解.

posted @ 2014-11-14 00:06 海鸟 阅读(...) 评论(...) 编辑 收藏