作者:jicanmeng
时间:2015年09月14日
关于sed有两个重要的概念,pattern space 和 hold buffer。每次sed从文件中读取一行后,都会放到pattern space中。hold buffer可以看做是一个临时的暂存区。
sed的工作原理如下:
while(input stream中有剩余的内容) { 从input stream读入下一行; 去除行尾的换行符'\n',然后放到pattern space中; 对pattern space中的这一行文本执行script中的内容; 对pattern space中的内容添加换行符'\n',再输出到output stream中。 }每次循环称为一个cycle。
sed命令的格式如下:
sed [OPTION] {script} [input-file]
常用的options如下: -n --quiet --silent suppress automatic printing of pattern space 执行完script中的命令后,不会将pattern space中的内容输出到output stream中。这个选项常常和p命令一起用。 -e SCRIPT --expression=SCRIPT add the script to the commands to be executed 指定执行某个script。可以有多个-e选项同时使用。 -f script-file --file=script-file add the contents of script-file to the commands to be executed 指定执行某个文件中的script。 -r --regexp-extended use extended regular expressions in the script. sed默认使用基本的正则表达式。使用-r选项会使用扩展的正则表达式。 -i[SUFFIX] --in-place[=SUFFIX] 直接修改输入文件。如果指定后缀,例如-i.bak,那么会首先创建一个.bak文件,然后再修改输入文件。
script的格式如下:
[address[,address]]command
address可以直接使用数字表示,表示对第几行执行命令。例如2d表示删除第2行的内容,2,7d表示删除2到7行的内容。 address也可以使用正则表达式的形式表示,表示对符合要求的某行执行命令。例如/abcd/d表示将包含有abcd字符的行删除,/abcd/,/defg/d表示将从包含abcd的行开始,一直到包含defg的行结束,全部删除。(这里特别需要注意的是,使用两个正则表达式表示范围时,第一个正则表达式表示打开功能,第二个正则表达式表示关闭功能。对于本例来说,如果文件中第3行包含abcd,第5行包含defg,第9行包含abcd,然后一直到文件末尾都不包含defg,那么会删除第3行到第5行的内容和第9行到文件末尾的内容。)
符号!放在某个address后面,表示对于这个address,不执行对应的命令。
符号$表示最后一行。
符号;用于分隔开多个command。等价于使用多个-e选项。
符号{}用于组合多个命令,各个命令之间使用;隔开。
s命令的格式如下:s/pattern/replacement/flags
常见的flag有四种:
sed "s/my/your/" pets.txt
sed "s/my/your/1" pets.txt
sed "s/my/your/2" pets.txt
sed "s/my/your/g" pets.txt
sed "s/my/your/3g" pets.txt
sed "3s/my/your/g" pets.txt
sed "3,6s/my/your/g" pets.txt
sed -n "s/test/abc/p" pets.txt
sed -n "s/test/abc/1p" pets.txt
sed -n "s/test/abc/2p" pets.txt
sed -n "s/test/abc/w result.txt" pets.txt
sed -n "s/test/abc/1w result.txt" pets.txt
sed -n "s/test/abc/2w result.txt" pets.txt
从上面可以看出来,s命令使用正斜线/来分隔各个字段。如果pattern或replacement中也出现了正斜线/,那么必须要使用反斜线进行转义。例如:将/etc/passwd文件中的/bin/bash替换为/bin/csh:
sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd
这样看起来就非常不方便。为了解决这个问题,sed命令允许使用其它字符来作为s命令的字段分隔符,例如!,%都可以。info sed中时这样描述的:
The `/' characters may be uniformly replaced by any other single character within any given `s' command. The `/' character (or whatever other character is used in its stead) can appear in the REGEXP or REPLACEMENT only if it is preceded by a `\' character.上面这个例子就可以写为:
sed 's!/bin/bash!/bin/csh!' /etc/passwd
echo "Test Line 1" | sed '1i hello,world!'
echo "Test Line 1" | sed '1i\ hello,world!'
echo "Test Line 1" | sed '1i\
hello,world!\
hello,programmer!'
[jicanmeng@andy tmp]$ echo "Test Line 1" | sed '1i\ hello,world!'
hello,world!
Test Line 1
[jicanmeng@andy tmp]$ echo "Test Line 1" | sed '1i\
> hello,world\
> hello,programmer!'
hello,world!
hello,programmer!
Test Line 1
[jicanmeng@andy tmp]$
从上图可以看出,要插入多行文本时,需要对新文本的末尾都使用反斜线,直到你要插入的文本的最后一行。
sed "$ a This is my monkey, my monkey's name is wukong" pets.txt
sed "2 c This is my monkey, my monkey's name is wukong" pets.txt
sed "/fish/c This is my monkey, my monkey's name is wukong" pets.txt
p命令会打印pattern space的内容,=命令会打印当前正在处理的数据流的行号,l命令也会打印pattern space的内容,和p命令不同的是,l命令同时还把一些特殊字符打印了出来。P命令只会打印pattern space中的第一行。
sed -n '/fish/p' pets.txt
sed -n '/dog/,/fish/p' pets.txt
sed -n '1,/fish/p' pets.txt
sed '=' pets.txt
sed -n '/fish/{=;p}' pets.txt
sed -n 'l' pets.txt
sed '1,3s/my/your/g; 3,$s/This/That/g' pets.txt
sed -e '1,3s/my/your/g' -e '3,$s/This/That/g' my.txt
sed '3,6 {/This/d}' pets.txt
sed '3,6 {/This/{/fish/d}}' pets.txt
sed '1,${/This/d;s/^ *//g}' pets.txt
[jicanmeng@andy tmp]$ cat script1
s/brown/green/
s/fox/elephant/
s/dog/cat/
[jicanmeng@andy tmp]$ cat data1
The quick brown fox jumps over the lazy dog.
[jicanmeng@andy tmp]$ sed -f script1 data1
The quick green elephant jumps over the lazy cat.
[jicanmeng@andy tmp]$
y命令是唯一可以处理单个字符的sed编辑器命令。命令格式如下:
[address]y/inchars/outchars/
转换命令会进行inchars和outchars值的一对一映射。inchars中的第一个字符会被转换为outchars中的第一个字符,第二个字符会被转换为outchars中的第二个字符。这个映射会一直持续到处理完指定字符。如果inchars和outchars的长度不同,则sed编辑器会产生一条错误信息。
sed 'y/123/456' pets.txt
y命令是一个全局命令,也就是说,它会自动替换一行中出现所有的inchars中的字符,你无法指定只替换某个字符,也无法指定只替换某字符第几次出现的地方。
w命令表示写到文件中,r表示从文件读取数据。info sed文档中对r命令的描述是:
Queue the contents of FILENAME to be read and inserted into the output stream at the end of the current cycle, or when the next input line is read. Note that if FILENAME cannot be read, it is treated as if it were an empty file, without any error indication.
sed '1,2w test.txt' pets.txt
sed '2r test.txt' pets.txt
[jicanmeng@andy tmp]$ cat script1
s/brown/green/
s/fox/elephant/
s/dog/cat/
[jicanmeng@andy tmp]$ cat data1
The quick brown fox jumps over the lazy dog.
[jicanmeng@andy tmp]$ sed '/fox/{
> r data1
> d}' script1
s/brown/green/
The quick brown fox jumps over the lazy dog.
s/dog/cat/
[jicanmeng@andy tmp]$
两点需要注意:1. 对于r命令:在本次cycle中所有命令执行完毕后,下次cycle从data1文件中读取数据,data1文件的数据读取完毕后,再继续从script1文件读取数据。2. 对于r命令,不要和其它的命令写在一行,因为文件名时可以包含一些特殊字符的,写在一行往往会出现读取的文件不存在的问题导致出错。
通常sed编辑器会在本行上执行完所有的命令,才会读取数据流的下一行,进行下次的命令执行过程。n命令改变了这个流程。它会直接读取下一行,放到pattern space中,继续执行本次循环中n命令后面的命令。info sed文档中对n命令的描述是:
If auto-print is not disabled, print the pattern space, then, regardless, replace the pattern space with the next line of input.
N命令也会读取下一行,但不是替换pattern space的内容,而是附加到pattern space中,然后继续执行N命令后面的命令。info sed文档中对N命令的描述是:
Add a newline to the pattern space, then append the next line of input to the pattern space. If there is no more input then `sed' exits without processing any more commands.
[jicanmeng@andy tmp]$ cat data3
The first meeting of the Linux System
Administrator's group will be held on Tuesday.
All System Administrator should attend this meeting.
Thank you for your attentance.
[jicanmeng@andy tmp]$ sed 'N; s/System\(.\)Administrator/Desktop\1User/' data3
The first meeting of the Linux Desktop
User's group will be held on Tuesday.
All Desktop User should attend this meeting.
Thank you for your attentance.
[jicanmeng@andy tmp]$ sed '{
> N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/}' data3
The first meeting of the Linux Desktop
User's group will be held on Tuesday.
All Desktop User should attend this meeting.
Thank you for your attentance.
[jicanmeng@andy tmp]$
这个例子非常典型。使用一条命令就可以代替下面的三条命令。由此也可以看出,在正则表达式中,.符号也可以代表换行符\n。我们再通过一个例子看一看n和N的用法:
[jicanmeng@andy tmp]$ cat one.txt
I install linux on my computer
hello world
I like programming.
[jicanmeng@andy tmp]$ sed '=;n;=;=;=' one.txt
1
I install linux on my computer
2
2
2
hello world
3
I like programming
[jicanmeng@andy tmp]$ sed -n '=;n;=;=;=' one.txt
1
2
2
2
3
[jicanmeng@andy tmp]$ sed '=;N;=;=;=' one.txt
1
2
2
2
I install linux on my computer
hello world
3
I like programming
[jicanmeng@andy tmp]$
可以看出,当下一行没有数据时,无论是n还是N,都不会继续执行后面的命令了,而是直接打印pattern space,结束本次循环了。
和n命令和N命令相同,d命令和D命令也改变了sed的命令执行过程。d命令删除pattern space,然后从头开始执行命令,即开始下次循环。D命令仅仅删除pattern space的第一行,如果pattern space中还有数据,就不读下一行,而是从头再进行一次命令循环;如果pattern space中没有数据了,就读取下一行,进行一次命令循环。
info sed文档中对D命令的描述是:
Delete text in the pattern space up to the first newline. If any text is left, restart cycle with the resultant pattern space (without reading a new line of input), otherwise start a normal new cycle.
sed '/fish/d' pets.txt
sed '2d' pets.txt
sed '2,$d' pets.txt
[jicanmeng@andy tmp]$ cat one.txt
I install linux on my computer
hello world
I like programming.
[jicanmeng@andy tmp]$ sed '=;=;N;D;=;=;=' one.txt
1
1
2
2
3
3
I like programming
[jicanmeng@andy tmp]$
b命令表示跳转,格式如下:
[address]b [label]
address参数决定了哪行或哪些行的数据会触发跳转命令。label参数定义了要跳转到的位置。如果没有加label参数,跳转命令就会跳转到脚本的末尾。
[jicanmeng@andy tmp]$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
[jicanmeng@andy tmp]$
这个脚本有个问题。它永远都不会停下,因为总会跳转到start。我们可以用t命令来解决这个问题。测试(test)命令t会基于替换命令的输出跳转到一个标签。格式如下:
[address]b [label]
[jicanmeng@andy tmp]$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> t start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
[jicanmeng@andy tmp]$
[jicanmeng@andy tmp]$ echo "1234567" | sed '{
> :start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start
> }'
1,234,567
[jicanmeng@andy tmp]$
这个例子非常重要,它体现了正则表达式匹配中的"贪心法"原则。
[jicanmeng@andy tmp]$ echo "The cat sleeps in his hat." | sed 's/.cat/"&"/g'
The "cat" sleeps in his "hat".
[jicanmeng@andy tmp]$
sed 'G' test.txt
sed '$!G' test.txt
sed '/^$/d; $!G' test.txt
sed '=' test.txt
sed '=' test.txt | sed 'N; s/\n/ /'
[jicanmeng@andy tmp]$ sed '{
> :start
> $q
> N
> 11,$D
> b start
> }' /etc/passwd
[jicanmeng@andy tmp]$
这个例子很好,注意看11,$D
,如果文件内容小于10行,这句就不起作用。
sed '/./,/^$/!d' test.txt
这个例子也非常妙,区间是/./
到/^$/
,区间的开始地址匹配会匹配任何含有至少一个字符的行,区间的结束地址会匹配一个空行。在这个区间内的行不会被删除,其余的行都被删除了。
sed '/./,$!d' test.txt
[jicanmeng@andy tmp]$ sed '{
> :start
> /^\n*$/{$d; N; b start}
> }' test.txt
[jicanmeng@andy tmp]$
sed -i 's/aabb/ccdd/g' `grep -rl aabb .`
其中的-l选项表示只显示匹配到的文件名。