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. 把tony和jack两个单词对换了. \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'
就是错误的, 这个很好理解.