初识sed
http://www-128.ibm.com/developerworks/linux/library/l-sed1.html
Sed是轻量级流编辑器。它有很多优点,首先是轻量级的,其次它是流编辑器,它可以通过stdio或pipeline接受数据并编辑。因为数据能够很容易的通过pipe传递给sed,所以sed可以和其他命令一起执行复杂的文本处理功能。
Sed例子
Sed对于输入的数据执行任意的用户指定的编辑操作(或命令)。Sed是基于行的处理,在每一行数据上,执行所有的命令,并将结果输出到stdout上,然后处理下一行。所以它并不修改输入文件。
看个例子:
$sed –e ‘d’ /etc/services
执行这条命令,将会没有任何输出。因为我们调用了命令d(删除行的命令)。Sed打开文件/etc/services,读取一行到sed的pattern缓冲区中,执行删除行命令,输出pattern缓冲区的内容(内容为空,因为已经被删除)。这种动作重复到读取完毕文件的内容为止。
这个例子中,我们应注意到以下几点。首先/etc/services文件并没有被修改。因为sed仅仅从文件读取内容,并不会降处理后的pattern缓冲区写回文件。其次sed是基于行的。D命令并不会告诉sed一下子删除所有的内容,而是每次从文件中读取一行到sed内部的pattern缓冲区中。一旦sed读取一行到内部的pattern缓冲区中,它就执行d命令,并输出pattern的内容.后面我将介绍如何利用address ranges来控制在哪些行上执行命令。如果没有指示addresses的话,在所有行上执行命令。第三是使用单引号来指示d命令。命令最好用单引号来引用,以免shell解释。
用sed来删除输出流的(输出流即文件/ect/services内容)第一行
$sed –e ‘1d’ /etc/services | more
除了命令d前面的1,它和我们前面的d命令是一样的。1就代表了第一行。D命令前加了可选得numerical address。通过使用addresses,可以指示sed在哪些行上执行命令。
地址范围
我们看看如何指示address range。下例中,sed将删除输出的1-10行。
$sed –e ‘1,10d’ /etc/services | more
用逗号分割的两个address,sed将在这两个address之间的行执行命令。本例中,sed将删除1-10行,包括1行和10行。
如果我们想看文件/etc/services文件的内容,但是不想看注释。我们知道注释以#开头,为了过滤掉注释,我们使用sed来删除以#开头的行。
$sed –e ‘/^#/d’ /etc/services | more
现在我们分析一下运行机制。除去d命令,就剩下了/^#/,它是一种新的regular expression address。 Regular expression addresses总是由/来包围着。对于匹配了Regular express的行,执行regular express 之后的命。所以'/^#/'是regular expression. 下面来回顾一下regular expression。
可以使用regular expression来表达我们想查找文本的模式。如果你在命令行中曾经使用*,你就在使用同regular expression相似的东西,但还不完全是regular expression。下面是在regular expression中常用的字符。
字符
|
意义
|
^
|
匹配一行的开始字符
|
$
|
匹配一行的结束字符
|
.
|
匹配任意的单个字符
|
*
|
匹配0个多个前面的字符
|
[ ]
|
匹配[]之中的所有字符
|
我们看几个例子 :
regexp
|
意义
|
/./
|
匹配至少包含一个字符的行
|
/../
|
匹配至少包含两个字符的行
|
/^#/
|
匹配以#开头的行
|
/^$/
|
匹配空行
|
/}^/
|
匹配以}结尾的行
|
/} *^/
|
匹配以}结尾的行,但}之后可以有任意个空格
|
/[abc]/
|
匹配包含小写字符串abc的行
|
/^[abc]/
|
匹配以小写字符串abc开头的行
|
可以这样使用regexp:
$sed –e ‘/regexp/d’/path/to/my/test/file | more
这将导致sed删除匹配的所有行。如果告诉sed打印匹配regexp的行,删除不匹配regexp的行的话,将更容易理解regexp。如下例子:
$sed –n –e ‘/regexp/p’ /path/to/my/test/file | more
注意新的-n选项,他指示sed不印刷pattern space,除非有显示的命令指示他打印。再注意用p代替了命令d,p就是print命令。现在只有匹配的行被打印。
关于地址
到目前,我们使用了行地址,行范围地址,regexp地址。但是还有更多的指示地址的可能。
用,分割我们可以指示两个regexp,sed将在从第一个匹配regexp1的行开始,到第一个匹配regexp2的行结束的所有行上,执行命令。例如,下面的命令将打印从包含BEGIN行开始,到包含END行结束的所有行。
$sed –n –e ‘/BEGIN/,/END/p’ /my/test/file | more
如果没有发现BEGIN,将不打印任何数据。如果没有发现END,将打印从包含BEGIN的行开始的所有行。这是因为sed是stream-oriented的,它不知道END是否会出现。
如果只想打印c文件中的main函数。可以使用
Sed –n –e ‘/main[[:space:]]*(/,/^}/p’ sourcefile.c | more
该命令用到了2个regexp,/main[[:space:]]*(/和/^}/,1个命令p。第一个regexp将匹配main然后可以有任意个空格,tab,然后是(。这将匹配ANSI C main函数的声明。
我们看到[[:space:]]这将匹配TAB或space。[:space:]是character classes。他必须出现在[]之中。
下面我们看第二个regexp。’/^}’将匹配以}开头的行。如果代码的格式很好的话,这将匹配main函数的结尾处。
P命令显示的告诉sed打印行,因为-n禁止了默认的打印功能。
替换
我们看看sed的最有用的命令,替换命令。使用它,我们可以替换一个特定的字符串,regexp为另一个字符串。我们看一个最简单的例子:
$sed –e ‘s/foo/bar/’ myfile.txt
命令将第一个foo替换为bar,并将文件的内容输出到stdout。请注意仅仅替换第一个foo,通常这并不是我们想要的。我们想把所有的foo都替换为bar。命令如下:
$sed –e ‘s/foo/bar/g’ myfile.txt
在/之后追加的g选项指示sed执行全体替换。我们详细看看’s///’替换命令。首先它是一个命令,仅仅是一个命令。例子中没有指示任何的地址。这意味着替换命令也可以使用地址来控制在哪些行上执行替换。我们看个例子:
$sed –e ‘1,10s/enchantment/entrapment/g’ myfile.txt
这将导致前10行的enchantment被替换为entrapment。
$sed –e ‘/^$/,/^END/s/hills/mountains/g’myfile.txt
这个命令将把hills替换为mountains,地址范围为从空行开始,到以END开始的行结束。
关于命令's///'的另一点要说明的是,可以使用很多不同的选项来代替/字符。如果我们的查找串或替代传中包含/的话,我们可以在s字符后面指示不同的分隔符。举个例子,我们将把/usr/local替换为/usr:
$sed –e ‘s:/usr/local:/usr:g’mylist.txt
这个例子中,我们使用:作为分隔符。如果你想在regexp中使用分隔符的话,在分隔符的前面加一个/(转意符)
到目前为止,我们仅仅执行了简单的字符串替换。尽管很方便,有时我们也需要替换regexp。例如如下的命令将替换<>中间的所有字符为空字符。
$sed –e ‘/<.*>//g’ myfile.txt
这作为删除文件中的HTML tags的第一个尝试,但很遗憾,由于regexp的原因,它并不能正常工作。为什么呢?当sed匹配regexp时,它查找最长的匹配。当使用d,p命令时,这并没有问题。但是当我们使用s命令时,就相当的不同了。我们看个例子:
<b>This</b> is what <b>I</b> meant.
将被替换为:
Meant.
而不是我们期望的
This is what I meant.
幸运的是,我们能够很容易的避免这个问题。修改后的regexp为’<[^>]*>’,它指出<后面有任意个非>字符,然后是>字符。其实这执行了可能的最短匹配,而不是最长匹配。修改后的命令为:
$sed –e ‘s/<[^>]*>//g’ myfile.html
例子中[^>]指示了一个非>字符,后面的*指示了任意个非>字符。
关于字符匹配
' []'regexp语法有些额外的选项。为了声明一个字符范围,可以使用开始字符-结束字符来表达。如下例:
‘[a-z]*’
这将匹配任意个小写的a到z之间的所有字符。
另外'[:space:]' character class用来匹配空白符。
以下是可用的character classes:
Character class
|
描述
|
[:alnum:]
|
字母数字 [a-z A-Z 0-9]
|
[:alpha:]
|
字母 [a-z A-Z]
|
[:blank:]
|
空白符
|
[:cntrl:]
|
控制字符
|
[:digit:]
|
数字 [0-9]
|
[:graph:]
|
可视字符,不包含空白符
|
[:lower:]
|
小写字符[a-z]
|
[:print:]
|
非控制字符
|
[:punct:]
|
Punctuation characters
|
[:space:]
|
空格
|
[:upper:]
|
大写字符 [A-Z]
|
[:xdigit:]
|
16进制数[0-9 a-f A-F]
|
应尽可能的利用character classes。
高级替换
我们可以引用匹配的regexp的部分或全部,以便构建替代字符串。作为例子,我们想在数据的前面添加”ralph said:”,命令如下:
$sed –e ‘s/.*/Ralph said:&/’ origmsg.txt
输出如下:
Ralph said: Hi
Ralph said:
。。。
本例中,我们在替换字符串中使用&,它指示插入匹配的整个regexp。他也可以被多次使用。
/(,/)的使用
's///'命令可以在regexp中定义regions,并且在替代字符串中可以引用region。假如有如下文件:
Foo bar oni
Eeny meeny miny
Larry curly moe
现在我们想写脚本把eeny meeny miny替换为victor eeny-meeny von miny。为了做到这一点,我们写一个regexp来匹配由空格分离的三个字符串。
‘.* .* .*’
现在我们使用/(,/)来包装感兴趣的region.
‘/(.*/) /(.*/) /(.*/)’
这个regexp定义了3个逻辑的region,在替换字符串中可以引用这3个region。下面是最终的脚本:
$sed –e ‘s//(.*/) /(.*/) /(.*/)/Victor /1-/2 Von /3/’ myfile.txt
正如你看到的,用/x来引用region,x是从1开始的region号。
输出如下:
Victor foo-bar Von oni
Victor eeny-meeny Von miny
Victor larry-curly Von moe
随着你对sed的熟悉,你将能执行强有力的文字处理工作。
综合
如果我们编写复杂的脚本,将需要输入更多的命令。有以下几种方法来处理,第一,在命令中使用;分割。例如使用=命令输出行号,p命令来打印。
$sed –n –e ‘=;p’ myfile.txt
命令将被按照顺序,在数据行上被执行。
尽管使用;很方便,也有不能工作的情况。另一个选项时使用-e。例子如下:
$sed –n –e ‘=’ –e ‘p’ myfile.txt
然而,如果更加复杂的追加,插入命令的话,-e可能就不行了。例如对于复杂的多行脚本,最好的方式是使用命令文件。用-f来引用命令文件。
$sed –n –f mycommands.sed myfile.txt
同一地址的多个命令
有时需要再一个地址上执行多个命令。在使用’s///’命令替换程序中的字符串时,特别有用。为了在单一地址上执行多个命令,将sed命令保存在文件中,并用{}来将命令分组。
例子如下:
1,20{
s/[Ll]inux/GNU//Linux/g
s/samba/Samba/g
s/posix/POSIX/g
}
例子中对1到20行中的每一行,执行3个替代命令。当然也可以使用regexp adresses,或者两者的组合。
1,/^END/{
s/[Ll]inux/GNU//Linux/g
s/samba/Samba/g
s/posix/POSIX/g
p
}
从第一行到以END开始的行或文件的结束行为止的所有行上,执行所有的命令。
追加,插入,改变行
插入行命令如下:
i/
This line will be inserted before each line
如果你没有指出地址的话,它将要在每一行前插入该行。
输出如下:
This line will be inserted before each line
line 1 here
This line will be inserted before each line
line 2 here
This line will be inserted before each line
line 3 here
This line will be inserted before each line
line 4 here
如果想在当前行前插入多行,可以在行结束时加入/
如下所示:
i/
insert this line/
and this one/
and this one/
a命令将在当前行后插入一行,
a/
insert this line after each line. Thanks! :)
c命令将替换当前行
c/
You're history, original line! Muhahaha!
因为append, insert, change命令行需要输入多行命令,所以它需要写入命令文件中。
文本转换Text translation
第一个实用脚本是转换unix风格的文本到dos/windows风格。如你所知,dos/windows的文本文件在行结尾有CR(carriage return)和LF(line feed),而unix文本文件的每行仅有line feed。如下的脚本将帮你从unix风格到dos/windows风格进行转换。
$sed –e ‘s/$//r/’ myunix.txt > mydos.txt
Regexp中的$代表行的结尾,/r告诉sed在它之前插入carriage return。这样每行是以/r/n结尾的。
如下的脚本完成从dos/windows风格的文本到unix风格的文本的转换:
$sed –e ‘s/.$//’mydos.txt > myunix.txt
我们的脚本匹配每行的最后一个字符,正好是/r,我们将他替换为空。如果你发现最后一个字符被删除了,那么源文件就是unix风格的文本了。
行的逆序
Tac命令仅仅把文件的所有行逆序,而每一行的内容不变。
Tacing如下文件:
Foo
Bar
Oni
将产生如下的输出:
Oni
Bar
Foo
我们可以使用如下脚本完成相同的功能:
$sed –e ‘1!G;h;$!d’ forward.txt > backward.txt
我们解释一下该脚本的工作原理,首先它包含了3个命令,由;进行分割。’1!G’,’h’,’$!d’。如果第一个命令是’1G’,G命令将仅仅作用在第一行。但是另外的!代表地址的相反,意思是,G作用于除第一行之外的所有行。对于$!d来说,也是相似的。如果命令是 '$d',d将仅仅作用于最后一行,('$' address是指明最后一行的简单方法). 然而,有了!,d命令将作用于除最后一行之外的所有行。现在,我们来理解如何进行的逆序。
当我们执行脚本时,首先被执行的是h,(因为G不在第一行上执行)。该命令h拷贝pattern space到hold space(临时的缓冲区)。接着d命令被执行,它从pattern space中删除foo。所以在该行上的所有命令被执行完毕后,并没有打印任何数据。
现在第二行。Bar被读进pattern space之后,G被执行。他将把hold space的内容(”foo/n”)追加到pattern space中(”bar/n”),现在pattern space中的内容为(“bar/nfoo/n”)。h命令再把pattern space的内容拷贝到hold space中。D命令删除pattern space中的内容。也不打印任何内容。
最后一行,oni,除了不删除pattern space的内容之外,同样的步骤被执行。逆序后的数据被输出到stdout中。
SED(1) User Commands SED(1)
NAME
sed - stream editor for filtering and transforming text
SYNOPSIS
sed [OPTION]... {script-only-if-no-other-script} [input-file]...
DESCRIPTION
Sed is a stream editor. A stream editor is used to perform basic text
transformations on an input stream (a file or input from a pipeline).
While in some ways similar to an editor which permits scripted edits
(such as ed), sed works by making only one pass over the input(s), and
is consequently more efficient. But it is sed's ability to filter text
in a pipeline which particularly distinguishes it from other types of
editors.
-n, --quiet, --silent
suppress automatic printing of pattern space
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script-file
add the contents of script-file to the commands to be executed
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
-l N, --line-length=N
specify the desired line-wrap length for the `l' command
--posix
disable all GNU extensions.
-r, --regexp-extended
use extended regular expressions in the script.
-s, --separate
consider files as separate rather than as a single continuous
long stream.
-b, --binary
do not convert DOS to UNIX lineendings (only on systems support-
ing different lineendings).
-u, --unbuffered
load minimal amounts of data from the input files and flush the
output buffers more often
--help display this help and exit
--version
output version information and exit
If no -e, --expression, -f, or --file option is given, then the first
non-option argument is taken as the sed script to interpret. All
remaining arguments are names of input files; if no input files are
specified, then the standard input is read.
E-mail bug reports to: bonzini@gnu.org . Be sure to include the word
``sed'' somewhere in the ``Subject:'' field.
COMMAND SYNOPSIS
This is just a brief synopsis of sed commands to serve as a reminder to
those who already know sed; other documentation (such as the texinfo
document) must be consulted for fuller descriptions.
Zero-address ``commands''
: label
Label for b and t commands.
#comment
The comment extends until the next newline (or the end of a -e
script fragment).
} The closing bracket of a { } block.
Zero- or One- address commands
= Print the current line number.
a /
text Append text, which has each embedded newline preceded by a back-
slash.
i /
text Insert text, which has each embedded newline preceded by a back-
slash.
q Immediately quit the sed script without processing any more
input, except that if auto-print is not disabled the current
pattern space will be printed.
Q Immediately quit the sed script without processing any more
input.
r filename
Append text read from filename.
R filename
Append a line read from filename.
Commands which accept address ranges
{ Begin a block of commands (end with a }).
b label
Branch to label; if label is omitted, branch to end of script.
t label
If a s/// has done a successful substitution since the last
input line was read and since the last t or T command, then
branch to label; if label is omitted, branch to end of script.
T label
If no s/// has done a successful substitution since the last
input line was read and since the last t or T command, then
branch to label; if label is omitted, branch to end of script.
c /
text Replace the selected lines with text, which has each embedded
newline preceded by a backslash.
d Delete pattern space. Start next cycle.
D Delete up to the first embedded newline in the pattern space.
Start next cycle, but skip reading from the input if there is
still data in the pattern space.
h H Copy/append pattern space to hold space.
g G Copy/append hold space to pattern space.
x Exchange the contents of the hold and pattern spaces.
l List out the current line in a ``visually unambiguous'' form.
n N Read/append the next line of input into the pattern space.
p Print the current pattern space.
P Print up to the first embedded newline of the current pattern
space.
s/regexp/replacement/
Attempt to match regexp against the pattern space. If success-
ful, replace that portion matched with replacement. The
replacement may contain the special character & to refer to that
portion of the pattern space which matched, and the special
escapes /1 through /9 to refer to the corresponding matching
sub-expressions in the regexp.
w filename
Write the current pattern space to filename.
W filename
Write the first line of the current pattern space to filename.
y/source/dest/
Transliterate the characters in the pattern space which appear
in source to the corresponding character in dest.
Addresses
Sed commands can be given with no addresses, in which case the command
will be executed for all input lines; with one address, in which case
the command will only be executed for input lines which match that
address; or with two addresses, in which case the command will be exe-
cuted for all input lines which match the inclusive range of lines
starting from the first address and continuing to the second address.
Three things to note about address ranges: the syntax is addr1,addr2
(i.e., the addresses are separated by a comma); the line which addr1
matched will always be accepted, even if addr2 selects an earlier line;
and if addr2 is a regexp, it will not be tested against the line that
addr1 matched.
After the address (or address-range), and before the command, a ! may
be inserted, which specifies that the command shall only be executed if
the address (or address-range) does not match.
The following address types are supported:
number Match only the specified line number.
first~step
Match every step'th line starting with line first. For example,
``sed -n 1~2p'' will print all the odd-numbered lines in the
input stream, and the address 2~5 will match every fifth line,
starting with the second. (This is an extension.)
$ Match the last line.
/regexp/
Match lines matching the regular expression regexp.
/cregexpc
Match lines matching the regular expression regexp. The c may
be any character.
GNU sed also supports some special 2-address forms:
0,addr2
Start out in "matched first address" state, until addr2 is
found. This is similar to 1,addr2, except that if addr2 matches
the very first line of input the 0,addr2 form will be at the end
of its range, whereas the 1,addr2 form will still be at the
beginning of its range.
addr1,+N
Will match addr1 and the N lines following addr1.
addr1,~N
Will match addr1 and the lines following addr1 until the next
line whose input line number is a multiple of N.
REGULAR EXPRESSIONS
POSIX.2 BREs should be supported, but they aren't completely because of
performance problems. The /n sequence in a regular expression matches
the newline character, and similarly for /a, /t, and other sequences.